summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/dashboard/partners-service.ts28
-rw-r--r--lib/dashboard/service.ts9
-rw-r--r--lib/email-template/editor/template-content-editor.tsx9
-rw-r--r--lib/email-template/service.ts9
-rw-r--r--lib/email-template/table/template-table-columns.tsx14
-rw-r--r--lib/email-template/table/update-template-sheet.tsx26
-rw-r--r--lib/file-stroage.ts2
-rw-r--r--lib/form-list.zipbin12417 -> 0 bytes
-rw-r--r--lib/sedp/get-form-tags.ts124
-rw-r--r--lib/sedp/sync-form.ts18
-rw-r--r--lib/soap/mdg/send/vendor-master/action.ts5
-rw-r--r--lib/tech-vendors/repository.ts1
-rw-r--r--lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx4
-rw-r--r--lib/users/auth/verifyCredentails.ts1
-rw-r--r--lib/users/service.ts176
-rw-r--r--lib/vendor-document-list/dolce-upload-service.ts107
-rw-r--r--lib/vendor-document-list/enhanced-document-service.ts11
-rw-r--r--lib/vendor-document-list/import-service.ts287
-rw-r--r--lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx42
-rw-r--r--lib/vendor-document-list/ship/enhanced-documents-table.tsx76
-rw-r--r--lib/vendor-document-list/ship/import-from-dolce-button.tsx86
-rw-r--r--lib/vendor-document-list/ship/send-to-shi-button.tsx286
22 files changed, 804 insertions, 517 deletions
diff --git a/lib/dashboard/partners-service.ts b/lib/dashboard/partners-service.ts
index 327a16a9..ac8ca920 100644
--- a/lib/dashboard/partners-service.ts
+++ b/lib/dashboard/partners-service.ts
@@ -73,7 +73,6 @@ export async function getPartnersTeamDashboardData(domain: string): Promise<Part
}
});
- console.log('๐Ÿ“Š ํŒŒํŠธ๋„ˆ ํŒ€ ๋Œ€์‹œ๋ณด๋“œ ๊ฒฐ๊ณผ:', successfulResults);
return successfulResults;
} catch (error) {
console.error("ํŒŒํŠธ๋„ˆ ํŒ€ ๋Œ€์‹œ๋ณด๋“œ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹คํŒจ:", error);
@@ -98,7 +97,6 @@ export async function getPartnersUserDashboardData(domain: string): Promise<Part
return [];
}
- console.log(`๐Ÿ‘ค ์‚ฌ์šฉ์ž ID: ${userId}, ํšŒ์‚ฌ ID: ${companyId}`);
// ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ
const results = await Promise.allSettled(
@@ -176,7 +174,6 @@ export async function getPartnersDashboardData(domain: string): Promise<Partners
// Partners ํ…Œ์ด๋ธ”๋ณ„ ์ „์ฒด ํ†ต๊ณ„ ์กฐํšŒ (ํšŒ์‚ฌ ํ•„ํ„ฐ๋ง ํฌํ•จ)
async function getPartnersTableStats(config: TableConfig, companyId: string): Promise<PartnersDashboardStats> {
try {
- console.log(`\n๐Ÿ” ํŒŒํŠธ๋„ˆ ํ…Œ์ด๋ธ” ${config.tableName} ํ†ต๊ณ„ ์กฐํšŒ (ํšŒ์‚ฌ: ${companyId})`);
// 1๋‹จ๊ณ„: ํšŒ์‚ฌ๋ณ„ ์ด ๊ฐœ์ˆ˜ ํ™•์ธ
const totalQuery = `
@@ -184,10 +181,8 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
FROM "${config.tableName}"
WHERE "vendor_id" = '${companyId}'
`;
- console.log("Total SQL:", totalQuery);
const totalResult = await db.execute(sql.raw(totalQuery));
- console.log("Total ๊ฒฐ๊ณผ:", totalResult.rows[0]);
// 2๋‹จ๊ณ„: ํšŒ์‚ฌ๋ณ„ ์ƒํƒœ๊ฐ’ ๋ถ„ํฌ ํ™•์ธ
const statusQuery = `
@@ -197,10 +192,8 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
GROUP BY "${config.statusField}"
ORDER BY count DESC
`;
- console.log("Status SQL:", statusQuery);
const statusResult = await db.execute(sql.raw(statusQuery));
- console.log("Status ๊ฒฐ๊ณผ:", statusResult.rows);
// 3๋‹จ๊ณ„: ์ƒํƒœ๋ณ„ ๊ฐœ์ˆ˜ ์กฐํšŒ
const pendingValues = Object.entries(config.statusMapping)
@@ -215,11 +208,7 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
.filter(([_, mapped]) => mapped === 'completed')
.map(([original]) => original);
- console.log("ํŒŒํŠธ๋„ˆ ์ƒํƒœ ๋งคํ•‘:");
- console.log("- pending:", pendingValues);
- console.log("- inProgress:", inProgressValues);
- console.log("- completed:", completedValues);
-
+
let pendingCount = 0;
let inProgressCount = 0;
let completedCount = 0;
@@ -235,7 +224,6 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
const pendingResult = await db.execute(sql.raw(pendingQuery));
pendingCount = parseInt(pendingResult.rows[0]?.count || '0');
- console.log("Pending ๊ฐœ์ˆ˜:", pendingCount);
}
// In Progress ๊ฐœ์ˆ˜ (ํšŒ์‚ฌ ํ•„ํ„ฐ ํฌํ•จ)
@@ -249,7 +237,6 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
const inProgressResult = await db.execute(sql.raw(inProgressQuery));
inProgressCount = parseInt(inProgressResult.rows[0]?.count || '0');
- console.log("InProgress ๊ฐœ์ˆ˜:", inProgressCount);
}
// Completed ๊ฐœ์ˆ˜ (ํšŒ์‚ฌ ํ•„ํ„ฐ ํฌํ•จ)
@@ -263,7 +250,6 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
const completedResult = await db.execute(sql.raw(completedQuery));
completedCount = parseInt(completedResult.rows[0]?.count || '0');
- console.log("Completed ๊ฐœ์ˆ˜:", completedCount);
}
const stats = {
@@ -275,7 +261,6 @@ async function getPartnersTableStats(config: TableConfig, companyId: string): Pr
completed: completedCount
};
- console.log(`โœ… ํŒŒํŠธ๋„ˆ ${config.tableName} ์ตœ์ข… ํ†ต๊ณ„:`, stats);
return stats;
} catch (error) {
console.error(`โŒ ํŒŒํŠธ๋„ˆ ํ…Œ์ด๋ธ” ${config.tableName} ํ†ต๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜:`, error);
@@ -288,11 +273,9 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
try {
// ์‚ฌ์šฉ์ž ํ•„๋“œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๋นˆ ํ†ต๊ณ„ ๋ฐ˜ํ™˜
if (!hasUserFields(config)) {
- console.log(`โš ๏ธ ํŒŒํŠธ๋„ˆ ํ…Œ์ด๋ธ” ${config.tableName}์— ์‚ฌ์šฉ์ž ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`);
return createEmptyPartnersStats(config);
}
- console.log(`\n๐Ÿ‘ค ํŒŒํŠธ๋„ˆ ์‚ฌ์šฉ์ž ${userId}์˜ ${config.tableName} ํ†ต๊ณ„ ์กฐํšŒ (ํšŒ์‚ฌ: ${companyId})`);
// ์‚ฌ์šฉ์ž ์กฐ๊ฑด ์ƒ์„ฑ (ํšŒ์‚ฌ ํ•„ํ„ฐ ํฌํ•จ)
const userConditions = [];
@@ -318,10 +301,8 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
FROM "${config.tableName}"
WHERE "vendor_id" = '${companyId}' AND (${userConditionStr})
`;
- console.log("User Total SQL:", userTotalQuery);
const userTotalResult = await db.execute(sql.raw(userTotalQuery));
- console.log("User Total ๊ฒฐ๊ณผ:", userTotalResult.rows[0]);
// 2. ์‚ฌ์šฉ์ž + ํšŒ์‚ฌ ์ƒํƒœ๋ณ„ ๊ฐœ์ˆ˜
const pendingValues = Object.entries(config.statusMapping)
@@ -351,7 +332,6 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
const userPendingResult = await db.execute(sql.raw(userPendingQuery));
userPendingCount = parseInt(userPendingResult.rows[0]?.count || '0');
- console.log("User Pending ๊ฐœ์ˆ˜:", userPendingCount);
}
// User + Company In Progress ๊ฐœ์ˆ˜
@@ -365,7 +345,6 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
const userInProgressResult = await db.execute(sql.raw(userInProgressQuery));
userInProgressCount = parseInt(userInProgressResult.rows[0]?.count || '0');
- console.log("User InProgress ๊ฐœ์ˆ˜:", userInProgressCount);
}
// User + Company Completed ๊ฐœ์ˆ˜
@@ -379,7 +358,6 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
const userCompletedResult = await db.execute(sql.raw(userCompletedQuery));
userCompletedCount = parseInt(userCompletedResult.rows[0]?.count || '0');
- console.log("User Completed ๊ฐœ์ˆ˜:", userCompletedCount);
}
const stats = {
@@ -391,7 +369,6 @@ async function getPartnersUserTableStats(config: TableConfig, companyId: string,
completed: userCompletedCount
};
- console.log(`โœ… ํŒŒํŠธ๋„ˆ ์‚ฌ์šฉ์ž ${config.tableName} ์ตœ์ข… ํ†ต๊ณ„:`, stats);
return stats;
} catch (error) {
console.error(`โŒ ํŒŒํŠธ๋„ˆ ํ…Œ์ด๋ธ” ${config.tableName} ์‚ฌ์šฉ์ž ํ†ต๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜:`, error);
@@ -418,12 +395,10 @@ function hasUserFields(config: TableConfig): boolean {
// ๋””๋ฒ„๊น… ํ•จ์ˆ˜: Partners ์ „์šฉ
export async function simplePartnersTest(tableName: string, statusField: string, companyId: string) {
try {
- console.log(`\n๐Ÿงช ํŒŒํŠธ๋„ˆ ${tableName} ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ (ํšŒ์‚ฌ: ${companyId}):`);
// 1. ํšŒ์‚ฌ๋ณ„ ์ด ๊ฐœ์ˆ˜
const totalQuery = `SELECT COUNT(*) as total FROM "${tableName}" WHERE "vendor_id" = '${companyId}'`;
const totalResult = await db.execute(sql.raw(totalQuery));
- console.log("ํšŒ์‚ฌ๋ณ„ ์ด ๊ฐœ์ˆ˜:", totalResult.rows[0]);
// 2. ํšŒ์‚ฌ๋ณ„ ์ƒํƒœ ๋ถ„ํฌ
const statusQuery = `
@@ -434,7 +409,6 @@ export async function simplePartnersTest(tableName: string, statusField: string,
ORDER BY count DESC
`;
const statusResult = await db.execute(sql.raw(statusQuery));
- console.log("ํšŒ์‚ฌ๋ณ„ ์ƒํƒœ ๋ถ„ํฌ:", statusResult.rows);
return {
total: totalResult.rows[0],
diff --git a/lib/dashboard/service.ts b/lib/dashboard/service.ts
index 91ed5eb2..980938ad 100644
--- a/lib/dashboard/service.ts
+++ b/lib/dashboard/service.ts
@@ -64,7 +64,6 @@ export async function getTeamDashboardData(domain: string): Promise<DashboardSta
}
});
- console.log('๐Ÿ“Š ํŒ€ ๋Œ€์‹œ๋ณด๋“œ ๊ฒฐ๊ณผ:', successfulResults);
return successfulResults;
} catch (error) {
@@ -90,7 +89,6 @@ export async function getUserDashboardData(domain: string): Promise<UserDashboar
return [];
}
- console.log(`๐Ÿ‘ค ์‚ฌ์šฉ์ž ID: ${userId}`);
// ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ
const results = await Promise.allSettled(
@@ -259,7 +257,6 @@ async function getTableStats(config: TableConfig): Promise<DashboardStats> {
completed: completedCount
};
- console.log(`โœ… ${config.tableName} ์ตœ์ข… ํ†ต๊ณ„:`, stats);
return stats;
} catch (error) {
console.error(`โŒ ํ…Œ์ด๋ธ” ${config.tableName} ํ†ต๊ณ„ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜:`, error);
@@ -273,11 +270,9 @@ async function getUserTableStats(config: TableConfig, userId: string): Promise<D
try {
// ์‚ฌ์šฉ์ž ํ•„๋“œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๋นˆ ํ†ต๊ณ„ ๋ฐ˜ํ™˜
if (!hasUserFields(config)) {
- console.log(`โš ๏ธ ํ…Œ์ด๋ธ” ${config.tableName}์— ์‚ฌ์šฉ์ž ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`);
return createEmptyStats(config);
}
- console.log(`\n๐Ÿ‘ค ์‚ฌ์šฉ์ž ${userId}์˜ ${config.tableName} ํ†ต๊ณ„ ์กฐํšŒ`);
// ์‚ฌ์šฉ์ž ์กฐ๊ฑด ์ƒ์„ฑ
const userConditions = [];
@@ -334,7 +329,6 @@ async function getUserTableStats(config: TableConfig, userId: string): Promise<D
const userPendingResult = await db.execute(sql.raw(userPendingQuery));
userPendingCount = parseInt(userPendingResult.rows[0]?.count || '0');
- console.log("User Pending ๊ฐœ์ˆ˜:", userPendingCount);
}
// User In Progress ๊ฐœ์ˆ˜
@@ -411,16 +405,13 @@ export async function simpleTest(tableName: string, statusField: string) {
ORDER BY count DESC
`;
const statusResult = await db.execute(sql.raw(statusQuery));
- console.log("์ƒํƒœ ๋ถ„ํฌ:", statusResult.rows);
// 3. ํŠน์ • ์ƒํƒœ ํ…Œ์ŠคํŠธ
const draftQuery = `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${statusField}" = 'DRAFT'`;
const draftResult = await db.execute(sql.raw(draftQuery));
- console.log("DRAFT ๊ฐœ์ˆ˜:", draftResult.rows[0]);
const docConfirmedQuery = `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${statusField}" = 'Doc. Confirmed'`;
const docConfirmedResult = await db.execute(sql.raw(docConfirmedQuery));
- console.log("Doc. Confirmed ๊ฐœ์ˆ˜:", docConfirmedResult.rows[0]);
return {
total: totalResult.rows[0],
diff --git a/lib/email-template/editor/template-content-editor.tsx b/lib/email-template/editor/template-content-editor.tsx
index 4d31753c..08de53d2 100644
--- a/lib/email-template/editor/template-content-editor.tsx
+++ b/lib/email-template/editor/template-content-editor.tsx
@@ -167,7 +167,7 @@ export function TemplateContentEditor({ template, onUpdate }: TemplateContentEdi
subject,
content,
sampleData,
- version: template.version + 1
+ version: template.version ? template.version + 1 :0
})
} else {
toast.error(result.error || '์ €์žฅ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.')
@@ -188,12 +188,13 @@ export function TemplateContentEditor({ template, onUpdate }: TemplateContentEdi
template.slug,
sampleData,
content,
- // subject // ์ฃผ์„ ํ•ด์ œ
+ subject // ์ฃผ์„ ํ•ด์ œ
)
- if (result.success) {
+
+ if (result.success && result.data) {
setPreviewHtml(result.data.html)
- setPreviewSubject(result.data.subject)
+ setPreviewSubject(result.data.subjectHtml)
if (!silent) toast.success('๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.')
} else {
if (!silent) toast.error(result.error || '๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.')
diff --git a/lib/email-template/service.ts b/lib/email-template/service.ts
index 13aba77b..e3ab9bed 100644
--- a/lib/email-template/service.ts
+++ b/lib/email-template/service.ts
@@ -789,7 +789,7 @@ export async function updateTemplateAction(slug: string, data: {
content?: string;
description?: string;
sampleData?: Record<string, any>;
- updatedBy: string;
+ updatedBy: number;
}) {
try {
if (data.content) {
@@ -850,14 +850,17 @@ export async function previewTemplateAction(
slug: string,
data: Record<string, any>,
customContent?: string,
- // subject: string
+ subject?: string
) {
try {
+
+
const html = await previewTemplate(slug, data, customContent);
+ const subjectHtml = await previewTemplate(slug, data, subject);
return {
success: true,
- data: { html }
+ data: { html , subjectHtml}
};
} catch (error) {
return {
diff --git a/lib/email-template/table/template-table-columns.tsx b/lib/email-template/table/template-table-columns.tsx
index d20739cc..a678a20a 100644
--- a/lib/email-template/table/template-table-columns.tsx
+++ b/lib/email-template/table/template-table-columns.tsx
@@ -248,20 +248,20 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Templat
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem asChild>
- <Link href={`/evcp/templates/${template.slug}`}>
+ <Link href={`/evcp/email-template/${template.slug}`}>
<Eye className="mr-2 size-4" aria-hidden="true" />
- ๋ณด๊ธฐ
+ ์ƒ์„ธ ๋ณด๊ธฐ
</Link>
</DropdownMenuItem>
- <DropdownMenuItem
+ {/* <DropdownMenuItem
onClick={() => {
setRowAction({ type: "update", row })
}}
>
<Edit className="mr-2 size-4" aria-hidden="true" />
- ์ˆ˜์ •
- </DropdownMenuItem>
+ ์—…๋ฐ์ดํŠธ
+ </DropdownMenuItem> */}
<DropdownMenuItem
onClick={() => {
@@ -269,7 +269,7 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Templat
}}
>
<Copy className="mr-2 size-4" aria-hidden="true" />
- ๋ณต์ œ
+ ๋ณต์ œํ•˜๊ธฐ
</DropdownMenuItem>
<DropdownMenuSeparator />
@@ -281,7 +281,7 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Templat
className="text-destructive focus:text-destructive"
>
<Trash className="mr-2 size-4" aria-hidden="true" />
- ์‚ญ์ œ
+ ์‚ญ์ œํ•˜๊ธฐ
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
diff --git a/lib/email-template/table/update-template-sheet.tsx b/lib/email-template/table/update-template-sheet.tsx
index 58da0626..d3df93f0 100644
--- a/lib/email-template/table/update-template-sheet.tsx
+++ b/lib/email-template/table/update-template-sheet.tsx
@@ -7,6 +7,7 @@ import { Loader } from "lucide-react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
+import { useSession } from "next-auth/react"
import { Button } from "@/components/ui/button"
import {
@@ -55,6 +56,13 @@ interface UpdateTemplateSheetProps
export function UpdateTemplateSheet({ template, ...props }: UpdateTemplateSheetProps) {
const [isUpdatePending, startUpdateTransition] = React.useTransition()
+ const { data: session } = useSession();
+
+ // ๋˜๋Š” ๋” ์•ˆ์ „ํ•˜๊ฒŒ
+ if (!session?.user?.id) {
+ toast.error("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
+ return
+ }
const form = useForm<UpdateTemplateSchema>({
resolver: zodResolver(updateTemplateSchema),
@@ -85,8 +93,10 @@ export function UpdateTemplateSheet({ template, ...props }: UpdateTemplateSheetP
const { error } = await updateTemplateAction(template.slug, {
name: input.name,
description: input.description || undefined,
+ category: input.category === "none" ? undefined : input.category, // ์—ฌ๊ธฐ์„œ ๋ณ€ํ™˜
+
// category๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์ง€๋งŒ, ํ•„์š”์‹œ ํฌํ•จ
- updatedBy: currentUserId,
+ updatedBy: Number(session.user.id),
})
if (error) {
@@ -165,13 +175,13 @@ export function UpdateTemplateSheet({ template, ...props }: UpdateTemplateSheetP
</SelectTrigger>
</FormControl>
<SelectContent>
- <SelectItem value="">์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ</SelectItem>
- {TEMPLATE_CATEGORY_OPTIONS.map((option) => (
- <SelectItem key={option.value} value={option.value}>
- {option.label}
- </SelectItem>
- ))}
- </SelectContent>
+ <SelectItem value="none">์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ</SelectItem>
+ {TEMPLATE_CATEGORY_OPTIONS.map((option) => (
+ <SelectItem key={option.value} value={option.value}>
+ {option.label}
+ </SelectItem>
+ ))}
+</SelectContent>
</Select>
<FormMessage />
</FormItem>
diff --git a/lib/file-stroage.ts b/lib/file-stroage.ts
index beca05ee..eab52364 100644
--- a/lib/file-stroage.ts
+++ b/lib/file-stroage.ts
@@ -367,7 +367,7 @@ interface SaveBufferOptions {
userId?: string;
}
-interface SaveFileResult {
+export interface SaveFileResult {
success: boolean;
filePath?: string;
publicPath?: string;
diff --git a/lib/form-list.zip b/lib/form-list.zip
deleted file mode 100644
index 7312729e..00000000
--- a/lib/form-list.zip
+++ /dev/null
Binary files differ
diff --git a/lib/sedp/get-form-tags.ts b/lib/sedp/get-form-tags.ts
index d44e11f5..32f4403e 100644
--- a/lib/sedp/get-form-tags.ts
+++ b/lib/sedp/get-form-tags.ts
@@ -54,7 +54,7 @@ export async function importTagsFromSEDP(
processedCount: number;
excludedCount: number;
totalEntries: number;
- formCreated?: boolean; // ์ƒˆ๋กœ ์ถ”๊ฐ€: form์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€
+ formCreated?: boolean;
errors?: string[];
}> {
try {
@@ -67,29 +67,10 @@ export async function importTagsFromSEDP(
// SEDP API์—์„œ ํƒœ๊ทธ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
const tagData = await fetchTagDataFromSEDP(projectCode, formCode);
- // ๋ฐ์ดํ„ฐ ํ˜•์‹ ์ฒ˜๋ฆฌ
- const tableName = Object.keys(tagData)[0];
- if (!tableName || !tagData[tableName]) {
- throw new Error("Invalid tag data format from SEDP API");
- }
-
- const tagEntries: TagEntry[] = tagData[tableName];
- if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
- return {
- processedCount: 0,
- excludedCount: 0,
- totalEntries: 0,
- errors: ["No tag entries found in API response"]
- };
- }
-
- // ์ง„ํ–‰ ์ƒํ™ฉ ๋ณด๊ณ 
- if (progressCallback) progressCallback(20);
-
// ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ชจ๋“  DB ์ž‘์—… ์ฒ˜๋ฆฌ
return await db.transaction(async (tx) => {
- // ํ”„๋กœ์ ํŠธ ID ๊ฐ€์ ธ์˜ค๊ธฐ
- const projectRecord = await tx.select({ id: projects.id })
+ // ํ”„๋กœ์ ํŠธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (type ํฌํ•จ)
+ const projectRecord = await tx.select({ id: projects.id, type: projects.type })
.from(projects)
.where(eq(projects.code, projectCode))
.limit(1);
@@ -99,7 +80,74 @@ export async function importTagsFromSEDP(
}
const projectId = projectRecord[0].id;
+ const projectType = projectRecord[0].type;
+
+ // ํ”„๋กœ์ ํŠธ ํƒ€์ž…์— ๋”ฐ๋ผ packageCode๋ฅผ ์ฐพ์„ ATT_ID ๊ฒฐ์ •
+ const packageCodeAttId = projectType === "ship" ? "CM3003" : "ME5074";
+
+ // packageId๋กœ contractItem๊ณผ item ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
+ const contractItemRecord = await tx.select({ itemId: contractItems.itemId })
+ .from(contractItems)
+ .where(eq(contractItems.id, packageId))
+ .limit(1);
+
+ if (!contractItemRecord || contractItemRecord.length === 0) {
+ throw new Error(`Contract item not found for packageId: ${packageId}`);
+ }
+
+ const itemRecord = await tx.select({ packageCode: items.packageCode })
+ .from(items)
+ .where(eq(items.id, contractItemRecord[0].itemId))
+ .limit(1);
+
+ if (!itemRecord || itemRecord.length === 0) {
+ throw new Error(`Item not found for itemId: ${contractItemRecord[0].itemId}`);
+ }
+
+ const targetPackageCode = itemRecord[0].packageCode;
+
+ // ๋ฐ์ดํ„ฐ ํ˜•์‹ ์ฒ˜๋ฆฌ - tagData์˜ ์ฒซ ๋ฒˆ์งธ ํ‚ค ์‚ฌ์šฉ
+ const tableName = Object.keys(tagData)[0];
+ if (!tableName || !tagData[tableName]) {
+ throw new Error("Invalid tag data format from SEDP API");
+ }
+
+ const allTagEntries: TagEntry[] = tagData[tableName];
+
+ if (!Array.isArray(allTagEntries) || allTagEntries.length === 0) {
+ return {
+ processedCount: 0,
+ excludedCount: 0,
+ totalEntries: 0,
+ errors: ["No tag entries found in API response"]
+ };
+ }
+
+ // packageCode๋กœ ํ•„ํ„ฐ๋ง - ATTRIBUTES์—์„œ ์ง€์ •๋œ ATT_ID์˜ VALUE์™€ packageCode ๋น„๊ต
+ const tagEntries = allTagEntries.filter(entry => {
+ if (Array.isArray(entry.ATTRIBUTES)) {
+ const packageCodeAttr = entry.ATTRIBUTES.find(attr => attr.ATT_ID === packageCodeAttId);
+ if (packageCodeAttr && packageCodeAttr.VALUE === targetPackageCode) {
+ return true;
+ }
+ }
+ return false;
+ });
+
+ if (tagEntries.length === 0) {
+ return {
+ processedCount: 0,
+ excludedCount: 0,
+ totalEntries: allTagEntries.length,
+ errors: [`No tag entries found with ${packageCodeAttId} attribute value matching packageCode: ${targetPackageCode}`]
+ };
+ }
+
+ // ์ง„ํ–‰ ์ƒํ™ฉ ๋ณด๊ณ 
+ if (progressCallback) progressCallback(20);
+
+ // ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๊ธฐ์กด๊ณผ ๋™์ผ...
// form ID ๊ฐ€์ ธ์˜ค๊ธฐ - ์—†์œผ๋ฉด ์ƒ์„ฑ
let formRecord = await tx.select({ id: forms.id })
.from(forms)
@@ -139,7 +187,7 @@ export async function importTagsFromSEDP(
// tagClass ์กฐํšŒ (CLS_ID -> label)
let tagClassLabel = firstTag.CLS_ID; // ๊ธฐ๋ณธ๊ฐ’
if (firstTag.CLS_ID) {
- const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label }) // โœ… ์ˆ˜์ •
+ const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label })
.from(tagClasses)
.where(and(
eq(tagClasses.code, firstTag.CLS_ID),
@@ -147,8 +195,6 @@ export async function importTagsFromSEDP(
))
.limit(1);
-
-
if (tagClassRecord && tagClassRecord.length > 0) {
tagClassLabel = tagClassRecord[0].label;
}
@@ -159,14 +205,11 @@ export async function importTagsFromSEDP(
projectId,
);
- // ep๊ฐ€ "IMEP"์ธ ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
- // const formMappings = allFormMappings?.filter(mapping => mapping.ep === "IMEP") || [];
-
// ํ˜„์žฌ formCode์™€ ์ผ์น˜ํ•˜๋Š” ๋งคํ•‘ ์ฐพ๊ธฐ
const targetFormMapping = allFormMappings.find(mapping => mapping.formCode === formCode);
if (targetFormMapping) {
- console.log(`[IMPORT TAGS] Found IMEP form mapping for ${formCode}, creating form...`);
+ console.log(`[IMPORT TAGS] Found form mapping for ${formCode}, creating form...`);
// form ์ƒ์„ฑ
const insertResult = await tx
@@ -176,7 +219,7 @@ export async function importTagsFromSEDP(
formCode: targetFormMapping.formCode,
formName: targetFormMapping.formName,
eng: true, // ENG ๋ชจ๋“œ์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ eng: true
- im: targetFormMapping.ep === "IMEP" ? true:false
+ im: targetFormMapping.ep === "IMEP" ? true : false
})
.returning({ id: forms.id });
@@ -185,9 +228,9 @@ export async function importTagsFromSEDP(
console.log(`[IMPORT TAGS] Successfully created form:`, insertResult[0]);
} else {
- console.log(`[IMPORT TAGS] No IMEP form mapping found for formCode: ${formCode}`);
- console.log(`[IMPORT TAGS] Available IMEP mappings:`, formMappings.map(m => m.formCode));
- throw new Error(`Form ${formCode} not found and no IMEP mapping available for tag type ${tagTypeDescription}`);
+ console.log(`[IMPORT TAGS] No form mapping found for formCode: ${formCode}`);
+ console.log(`[IMPORT TAGS] Available mappings:`, allFormMappings.map(m => m.formCode));
+ throw new Error(`Form ${formCode} not found and no mapping available for tag type ${tagTypeDescription}`);
}
} else {
throw new Error(`Form not found for formCode: ${formCode} and contractItemId: ${packageId}, and no tags to derive form mapping`);
@@ -232,7 +275,7 @@ export async function importTagsFromSEDP(
// tagClass ์กฐํšŒ
let tagClassLabel = firstTag.CLS_ID;
if (firstTag.CLS_ID) {
- const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label }) // โœ… ์ˆ˜์ •
+ const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label })
.from(tagClasses)
.where(and(
eq(tagClasses.code, firstTag.CLS_ID),
@@ -250,9 +293,6 @@ export async function importTagsFromSEDP(
projectId,
);
- // ep๊ฐ€ "IMEP"์ธ ๊ฒƒ๋งŒ ํ•„ํ„ฐ๋ง
- // const formMappings = allFormMappings?.filter(mapping => mapping.ep === "IMEP") || [];
-
// ํ˜„์žฌ formCode์™€ ์ผ์น˜ํ•˜๋Š” ๋งคํ•‘ ์ฐพ๊ธฐ
const targetFormMapping = allFormMappings.find(mapping => mapping.formCode === formCode);
@@ -292,6 +332,9 @@ export async function importTagsFromSEDP(
const formId = formRecord[0].id;
+ // ๋‚˜๋จธ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์€ ๊ธฐ์กด๊ณผ ๋™์ผ...
+ // (์–‘์‹ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ, ํƒœ๊ทธ ์ฒ˜๋ฆฌ ๋“ฑ)
+
// ์–‘์‹ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
const formMetaRecord = await tx.select({ columns: formMetas.columns })
.from(formMetas)
@@ -388,7 +431,7 @@ export async function importTagsFromSEDP(
let tagClassLabel = tagEntry.CLS_ID; // ๊ธฐ๋ณธ๊ฐ’
let tagClassId = null; // ๊ธฐ๋ณธ๊ฐ’
if (tagEntry.CLS_ID) {
- const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label }) // โœ… ์ˆ˜์ •
+ const tagClassRecord = await tx.select({ id: tagClasses.id, label: tagClasses.label })
.from(tagClasses)
.where(and(
eq(tagClasses.code, tagEntry.CLS_ID),
@@ -414,7 +457,7 @@ export async function importTagsFromSEDP(
contractItemId: packageId,
formId: formId,
tagNo: tagEntry.TAG_NO,
- tagType: tagTypeDescription,
+ tagType: tagTypeDescription || "",
class: tagClassLabel,
tagClassId: tagClassId,
description: tagEntry.TAG_DESC || null,
@@ -644,7 +687,7 @@ export async function importTagsFromSEDP(
processedCount,
excludedCount,
totalEntries: tagEntries.length,
- formCreated, // ์ƒˆ๋กœ ์ถ”๊ฐ€: form์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€
+ formCreated,
errors: errors.length > 0 ? errors : undefined
};
});
@@ -654,7 +697,6 @@ export async function importTagsFromSEDP(
throw error;
}
}
-
/**
* SEDP API์—์„œ ํƒœ๊ทธ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
*
diff --git a/lib/sedp/sync-form.ts b/lib/sedp/sync-form.ts
index c3c88f07..f9e63caf 100644
--- a/lib/sedp/sync-form.ts
+++ b/lib/sedp/sync-form.ts
@@ -430,23 +430,7 @@ async function getRegisters(projectCode: string): Promise<Register[]> {
}
// ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ (๋‹จ์ผ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ ๋ฐฐ์—ด๋กœ ๋ž˜ํ•‘)
- let registers: Register[] = Array.isArray(data) ? data : [data];
-
- // MAP_CLS_ID๊ฐ€ ๋น„์–ด์žˆ์ง€ ์•Š๊ณ  REMARK๊ฐ€ vd, VD, vD, Vd ์ค‘ ํ•˜๋‚˜์ธ ๋ ˆ์ง€์Šคํ„ฐ๋งŒ ํ•„ํ„ฐ๋ง
- registers = registers.filter(register => {
- // ์‚ญ์ œ๋œ ๋ ˆ์ง€์Šคํ„ฐ ์ œ์™ธ
- if (register.DELETED) return false;
-
- // MAP_CLS_ID ๋ฐฐ์—ด์ด ์กด์žฌํ•˜๊ณ  ์š”์†Œ๊ฐ€ ํ•˜๋‚˜ ์ด์ƒ ์žˆ๋Š”์ง€ ํ™•์ธ
- const hasValidMapClsId = Array.isArray(register.MAP_CLS_ID) && register.MAP_CLS_ID.length > 0;
-
- // REMARK๊ฐ€ 'vd_' ๋˜๋Š” 'vd' ํฌํ•จ ํ™•์ธ (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์ด)
- const remarkLower = register.REMARK && register.REMARK.toLowerCase();
- const hasValidRemark = remarkLower && (remarkLower.includes('vd'));
-
- // ๋‘ ์กฐ๊ฑด ๋ชจ๋‘ ์ถฉ์กฑํ•ด์•ผ ํ•จ
- return hasValidMapClsId && hasValidRemark;
- });
+ const registers: Register[] = Array.isArray(data) ? data : [data];
console.log(`ํ”„๋กœ์ ํŠธ ${projectCode}์—์„œ ${registers.length}๊ฐœ์˜ ์œ ํšจํ•œ ๋ ˆ์ง€์Šคํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.`);
return registers;
diff --git a/lib/soap/mdg/send/vendor-master/action.ts b/lib/soap/mdg/send/vendor-master/action.ts
index 24112316..ec267bbb 100644
--- a/lib/soap/mdg/send/vendor-master/action.ts
+++ b/lib/soap/mdg/send/vendor-master/action.ts
@@ -18,6 +18,7 @@ import {
} from "@/db/schema/MDG/mdg";
import { eq, sql, desc } from "drizzle-orm";
import { withSoapLogging } from "../../utils";
+// public ๊ฒฝ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ. ํ˜•์ œ๊ฒฝ๋กœ ์‚ฌ์šฉ. (์ฒ˜๋ฆฌ์™„๋ฃŒ)
import fs from 'fs';
import path from 'path';
import { XMLBuilder } from 'fast-xml-parser';
@@ -49,7 +50,7 @@ function parseCsv(content: string): CsvField[] {
}
// ๋ชจ๋“ˆ ์ดˆ๊ธฐํ™” ์‹œ CSV ๋กœ๋“œ
-const CSV_PATH = path.join(process.cwd(), 'public', 'wsdl', 'P2MD3007_AO.csv');
+const CSV_PATH = path.join(__dirname, 'P2MD3007_AO.csv');
let CSV_FIELDS: CsvField[] = [];
try {
const csvRaw = fs.readFileSync(CSV_PATH, 'utf-8');
@@ -699,5 +700,3 @@ export async function getVendorSendStatistics(): Promise<{
throw error;
}
}
-
-
diff --git a/lib/tech-vendors/repository.ts b/lib/tech-vendors/repository.ts
index a273bf50..6f9aafbf 100644
--- a/lib/tech-vendors/repository.ts
+++ b/lib/tech-vendors/repository.ts
@@ -349,7 +349,6 @@ export async function getVendorWorkTypes(
})
.from(techVendorPossibleItems)
.where(eq(techVendorPossibleItems.vendorId, vendorId));
- console.log("possibleItems", possibleItems);
if (!possibleItems.length) {
return [];
}
diff --git a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
index 41572a93..acf67497 100644
--- a/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
+++ b/lib/techsales-rfq/table/detail-table/rfq-detail-table.tsx
@@ -730,14 +730,14 @@ export function RfqDetailTables({ selectedRfq, maxHeight }: RfqDetailTablesProps
onSuccess={handleRefreshData}
/>
- {/* ๋‹ค์ค‘ ๋ฒค๋” ์‚ญ์ œ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ */}
+ {/* ๋‹ค์ค‘ ๋ฒค๋” ์‚ญ์ œ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ
<DeleteVendorDialog
open={deleteConfirmDialogOpen}
onOpenChange={setDeleteConfirmDialogOpen}
vendors={selectedRows}
onConfirm={executeDeleteVendors}
isLoading={isDeletingVendors}
- />
+ /> */}
{/* ๊ฒฌ์  ํžˆ์Šคํ† ๋ฆฌ ๋‹ค์ด์–ผ๋กœ๊ทธ */}
<QuotationHistoryDialog
diff --git a/lib/users/auth/verifyCredentails.ts b/lib/users/auth/verifyCredentails.ts
index 83a2f276..a5dbab41 100644
--- a/lib/users/auth/verifyCredentails.ts
+++ b/lib/users/auth/verifyCredentails.ts
@@ -3,6 +3,7 @@
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
+// (์ฒ˜๋ฆฌ ๋ถˆํ•„์š”) ํ‚ค ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ fs ๋ชจ๋“ˆ ์‚ฌ์šฉ, ํ˜•์ œ ๊ฒฝ๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ public ๊ฒฝ๋กœ ์•„๋‹ˆ๋ฏ€๋กœ ํŒŒ์ผ์ด ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ.
import fs from 'fs';
import path from 'path';
import { eq, and, desc, gte, count } from 'drizzle-orm';
diff --git a/lib/users/service.ts b/lib/users/service.ts
index 7a635113..80c346fa 100644
--- a/lib/users/service.ts
+++ b/lib/users/service.ts
@@ -5,7 +5,6 @@ import { Otp } from '@/types/user';
import { getAllUsers, createUser, getUserById, updateUser, deleteUser, getUserByEmail, createOtp,getOtpByEmailAndToken, updateOtp, findOtpByEmail ,getOtpByEmailAndCode, findAllRoles, getRoleAssignedUsers} from './repository';
import logger from '@/lib/logger';
import { Role, roles, userRoles, users, userView, type User } from '@/db/schema/users';
-import { saveDocument } from '../storage';
import { GetSimpleUsersSchema, GetUsersSchema } from '../admin-users/validations';
import { revalidatePath, revalidateTag, unstable_cache, unstable_noStore } from 'next/cache';
import { filterColumns } from '../filter-columns';
@@ -15,6 +14,7 @@ import { getErrorMessage } from "@/lib/handle-error";
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/app/api/auth/[...nextauth]/route"
import { and, or, desc, asc, ilike, eq, isNull, sql, count, inArray, ne } from "drizzle-orm";
+import { SaveFileResult, saveFile } from '../file-stroage';
interface AssignUsersArgs {
roleId: number
@@ -232,41 +232,159 @@ export async function findEmailTemp(email: string) {
}
}
-export async function updateUserProfileImage(formData: FormData) {
+/**
+ * ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ๊ฒฐ๊ณผ ์ธํ„ฐํŽ˜์ด์Šค
+ */
+interface ProfileUpdateResult {
+ success: boolean;
+ user?: any;
+ error?: string;
+ duration?: number;
+ fileInfo?: {
+ fileName: string;
+ originalName: string;
+ fileSize: string;
+ publicPath: string;
+ };
+ securityChecks?: {
+ extensionCheck: boolean;
+ fileNameCheck: boolean;
+ sizeCheck: boolean;
+ mimeTypeCheck: boolean;
+ contentCheck: boolean;
+ };
+}
+
+/**
+ * ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ (ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ์šฉ)
+ */
+const validateUserData = {
+ validateEmail(email: string): boolean {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ },
+
+ validateName(name: string): boolean {
+ return name.length >= 2 &&
+ name.length <= 50 &&
+ !/[<>:"'|?*]/.test(name);
+ },
+};
+
+/**
+ * ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์ฝ๊ธฐ ์‰ฌ์šด ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜
+ */
+const formatFileSize = (bytes: number): string => {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+};
+
+/**
+ * ๋ณด์•ˆ ๊ฐ•ํ™”๋œ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜
+ * (file-storage.ts์˜ saveFile ํ•จ์ˆ˜ ํ™œ์šฉ)
+ */
+export async function updateUserProfileImage(formData: FormData): Promise<ProfileUpdateResult> {
+ const startTime = Date.now();
+
// 1) FormData์—์„œ ๋ฐ์ดํ„ฐ ๊บผ๋‚ด๊ธฐ
- const file = formData.get("file") as File | null
- const userId = Number(formData.get("userId"))
- const name = formData.get("name") as string
- const email = formData.get("email") as string
-
- // 2) ๊ธฐ๋ณธ์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
- if (!file) {
- throw new Error("No file found in the FormData.")
- }
- if (!userId) {
- throw new Error("userId is required.")
- }
+ const file = formData.get("file") as File | null;
+ const userId = Number(formData.get("userId"));
+ const name = formData.get("name") as string;
+ const email = formData.get("email") as string;
try {
- // 3) ํŒŒ์ผ ์ €์žฅ (ํ•ด์‹œ ์ƒ์„ฑ)
- const directory = './public/profiles'
- const { hashedFileName } = await saveDocument(file, directory)
-
- // 4) DB ์—…๋ฐ์ดํŠธ
- const imageUrl = hashedFileName
- const data = { name, email, imageUrl }
- const user = await updateUser(userId, data)
+ // 2) ๊ธฐ๋ณธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
+ if (!file) {
+ throw new Error("ํŒŒ์ผ์ด ์ œ๊ณต๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.");
+ }
+
+ if (!userId || userId <= 0) {
+ throw new Error("์œ ํšจํ•œ ์‚ฌ์šฉ์ž ID๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
+ }
+
+ // 3) ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฒ€์ฆ (ํŒŒ์ผ ์ €์žฅ ์ „์— ๋ฏธ๋ฆฌ ์ฒดํฌ)
+ if (name && !validateUserData.validateName(name)) {
+ throw new Error("์œ ํšจํ•˜์ง€ ์•Š์€ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค (2-50์ž, ํŠน์ˆ˜๋ฌธ์ž ์ œํ•œ).");
+ }
+
+ if (email && !validateUserData.validateEmail(email)) {
+ throw new Error("์œ ํšจํ•˜์ง€ ์•Š์€ ์ด๋ฉ”์ผ ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค.");
+ }
+
+ console.log("๐Ÿ“ค ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋ฐ์ดํŠธ ์‹œ์ž‘:", {
+ fileName: file.name,
+ fileSize: formatFileSize(file.size),
+ userId,
+ });
+
+ // 4) file-storage.ts์˜ saveFile ํ•จ์ˆ˜๋กœ ๋ณด์•ˆ ๊ฒ€์ฆ + ์ €์žฅ (ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌ!)
+ const saveResult: SaveFileResult = await saveFile({
+ file,
+ directory: 'profiles', // './public/profiles'์—์„œ 'profiles'๋กœ ๋ณ€๊ฒฝ
+ originalName: file.name,
+ userId: userId.toString(),
+ });
+
+ // 5) ํŒŒ์ผ ์ €์žฅ ์‹คํŒจ ์‹œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
+ if (!saveResult.success) {
+ throw new Error(saveResult.error || "ํŒŒ์ผ ์ €์žฅ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");
+ }
+
+ console.log("โœ… ํŒŒ์ผ ์ €์žฅ ์„ฑ๊ณต:", {
+ fileName: saveResult.fileName,
+ publicPath: saveResult.publicPath,
+ securityChecks: saveResult.securityChecks,
+ });
+
+ // 6) DB ์—…๋ฐ์ดํŠธ (file-storage.ts์—์„œ ๋ฐ˜ํ™˜๋œ publicPath ์‚ฌ์šฉ)
+ const imageUrl = saveResult.publicPath; // ์›น์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ฒฝ๋กœ
+ const data = { name, email, imageUrl };
+ const user = await updateUser(userId, data);
+
if (!user) {
- // updateUser๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด, DB ์—…๋ฐ์ดํŠธ ์‹คํŒจ ํ˜น์€ ํ•ด๋‹น ์œ ์ €๊ฐ€ ์—†์Œ
- throw new Error(`User with id=${userId} not found or update failed.`)
+ // DB ์—…๋ฐ์ดํŠธ ์‹คํŒจ ์‹œ ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ฆฌ
+ // TODO: file-storage.ts์˜ deleteFile ํ•จ์ˆ˜ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ ์‚ญ์ œ ๊ณ ๋ ค
+ throw new Error(`์‚ฌ์šฉ์ž ID=${userId}๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.`);
}
- // 5) ์„ฑ๊ณต ์‹œ ์„ฑ๊ณต ์ •๋ณด ๋ฐ˜ํ™˜
- return { success: true, user }
+ // 7) ์„ฑ๊ณต ๋ฐ˜ํ™˜
+ const duration = Date.now() - startTime;
+
+ console.log("๐ŸŽ‰ ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต:", {
+ userId,
+ imageUrl,
+ duration: `${duration}ms`,
+ });
+
+ return {
+ success: true,
+ user,
+ duration,
+ fileInfo: {
+ fileName: saveResult.fileName!,
+ originalName: saveResult.originalName!,
+ fileSize: formatFileSize(saveResult.fileSize!),
+ publicPath: saveResult.publicPath!,
+ },
+ securityChecks: saveResult.securityChecks,
+ };
+
} catch (err: any) {
- // DB ์—…๋ฐ์ดํŠธ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ๋‚˜ saveDocument ๋‚ด๋ถ€ ์—๋Ÿฌ ๋“ฑ์„ ์ฒ˜๋ฆฌ
- console.error("[updateUserProfileImage] Error:", err)
- throw new Error(err.message ?? "Failed to update user profile.")
+ const duration = Date.now() - startTime;
+ const errorMessage = err.message ?? "ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
+
+ console.error("โŒ [updateUserProfileImage] Error:", err);
+
+ return {
+ success: false,
+ error: errorMessage,
+ duration,
+ };
}
}
diff --git a/lib/vendor-document-list/dolce-upload-service.ts b/lib/vendor-document-list/dolce-upload-service.ts
index d98a4c70..032b028c 100644
--- a/lib/vendor-document-list/dolce-upload-service.ts
+++ b/lib/vendor-document-list/dolce-upload-service.ts
@@ -17,6 +17,19 @@ export interface DOLCEUploadResult {
}
}
+interface ResultData {
+ FileId: string;
+ UploadId: string;
+ FileSeq: number;
+ FileName: string;
+ FileRelativePath: string;
+ FileSize: number;
+ FileCreateDT: string; // ISO string format
+ FileWriteDT: string; // ISO string format
+ OwnerUserId: string;
+}
+
+
interface FileReaderConfig {
baseDir: string;
isProduction: boolean;
@@ -339,8 +352,10 @@ private async uploadFiles(
uploadId: string // ์ด๋ฏธ ์ƒ์„ฑ๋œ UploadId๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์Œ
): Promise<Array<{ uploadId: string, fileId: string, filePath: string }>> {
const uploadResults = []
+ const resultDataArray: ResultData[] = []
- for (const attachment of attachments) {
+ for (let i = 0; i < attachments.length; i++) {
+ const attachment = attachments[i]
try {
// FileId๋งŒ ์ƒˆ๋กœ ์ƒ์„ฑ (UploadId๋Š” ์ด๋ฏธ ์ƒ์„ฑ๋œ ๊ฒƒ ์‚ฌ์šฉ)
const fileId = uuidv4()
@@ -386,6 +401,23 @@ private async uploadFiles(
filePath: dolceFilePath
})
+ // ResultData ๊ฐ์ฒด ์ƒ์„ฑ (PWPUploadResultService ํ˜ธ์ถœ์šฉ)
+ const fileStats = await this.getFileStats(attachment.filePath) // ํŒŒ์ผ ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ
+
+ const resultData: ResultData = {
+ FileId: fileId,
+ UploadId: uploadId,
+ FileSeq: i + 1, // 1๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ์‹œํ€€์Šค
+ FileName: attachment.fileName,
+ FileRelativePath: dolceFilePath,
+ FileSize: fileStats.size,
+ FileCreateDT: fileStats.birthtime.toISOString(),
+ FileWriteDT: fileStats.mtime.toISOString(),
+ OwnerUserId: userId
+ }
+
+ resultDataArray.push(resultData)
+
console.log(`โœ… File uploaded successfully: ${attachment.fileName} -> ${dolceFilePath}`)
console.log(`โœ… DB updated for attachment ID: ${attachment.id}`)
@@ -395,9 +427,82 @@ private async uploadFiles(
}
}
+ // ๋ชจ๋“  ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ PWPUploadResultService ํ˜ธ์ถœ
+ if (resultDataArray.length > 0) {
+ try {
+ await this.finalizeUploadResult(resultDataArray)
+ console.log(`โœ… Upload result finalized for UploadId: ${uploadId}`)
+ } catch (error) {
+ console.error(`โŒ Failed to finalize upload result for UploadId: ${uploadId}`, error)
+ // ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ๊ฒฐ๊ณผ ์ €์žฅ ์‹คํŒจ - ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ๊ณ„์† ์ง„ํ–‰
+ }
+ }
+
return uploadResults
}
+
+private async finalizeUploadResult(resultDataArray: ResultData[]): Promise<void> {
+ const url = `${this.UPLOAD_SERVICE_URL}/PWPUploadResultService.ashx`
+
+ try {
+ const jsonData = JSON.stringify(resultDataArray)
+ const dataBuffer = Buffer.from(jsonData, 'utf-8')
+
+ console.log(`Calling PWPUploadResultService with ${resultDataArray.length} files`)
+ console.log('ResultData:', JSON.stringify(resultDataArray, null, 2))
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: dataBuffer
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ throw new Error(`PWPUploadResultService failed: HTTP ${response.status} - ${errorText}`)
+ }
+
+ const result = await response.text()
+
+ if (result !== 'Success') {
+ throw new Error(`PWPUploadResultService returned unexpected result: ${result}`)
+ }
+
+ console.log('โœ… PWPUploadResultService call successful')
+
+ } catch (error) {
+ console.error('โŒ PWPUploadResultService call failed:', error)
+ throw error
+ }
+}
+
+// ํŒŒ์ผ ํ†ต๊ณ„ ์ •๋ณด ์กฐํšŒ ํ—ฌํผ ๋ฉ”์„œ๋“œ (ํŒŒ์ผ์‹œ์Šคํ…œ์—์„œ ํŒŒ์ผ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด)
+private async getFileStats(filePath: string): Promise<{ size: number, birthtime: Date, mtime: Date }> {
+ try {
+ // Node.js ํ™˜๊ฒฝ์ด๋ผ๋ฉด fs.stat ์‚ฌ์šฉ
+ const fs = require('fs').promises
+ const stats = await fs.stat(filePath)
+
+ return {
+ size: stats.size,
+ birthtime: stats.birthtime,
+ mtime: stats.mtime
+ }
+ } catch (error) {
+ console.warn(`Could not get file stats for ${filePath}, using defaults`)
+ // ํŒŒ์ผ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
+ const now = new Date()
+ return {
+ size: 0,
+ birthtime: now,
+ mtime: now
+ }
+ }
+}
+
/**
* ๋ฌธ์„œ ์ •๋ณด ์—…๋กœ๋“œ (DetailDwgReceiptMgmtEdit)
*/
diff --git a/lib/vendor-document-list/enhanced-document-service.ts b/lib/vendor-document-list/enhanced-document-service.ts
index e01283dc..6fe8feb7 100644
--- a/lib/vendor-document-list/enhanced-document-service.ts
+++ b/lib/vendor-document-list/enhanced-document-service.ts
@@ -20,7 +20,7 @@ import type {
Revision
} from "@/types/enhanced-documents"
import { GetVendorShipDcoumentsSchema } from "./validations"
-import { contracts, users } from "@/db/schema"
+import { contracts, users, vendors } from "@/db/schema"
// ์Šคํ‚ค๋งˆ ํƒ€์ž… ์ •์˜
export interface GetEnhancedDocumentsSchema {
@@ -1092,11 +1092,12 @@ export async function getDocumentDetails(documentId: number) {
// ๋ฒค๋” ์ •๋ณด ์กฐํšŒ
const [vendorInfo] = await tx
.select({
- vendorName: simplifiedDocumentsView.vendorName,
- vendorCode: simplifiedDocumentsView.vendorCode,
+ vendorName: vendors.vendorName,
+ vendorCode: vendors.vendorCode,
})
- .from(simplifiedDocumentsView)
- .where(eq(simplifiedDocumentsView.contractId, contractIds[0]))
+ .from(contracts)
+ .leftJoin(vendors, eq(contracts.vendorId, vendors.id))
+ .where(eq(contracts.id, contractIds[0]))
.limit(1)
return { data, total, drawingKind, vendorInfo }
diff --git a/lib/vendor-document-list/import-service.ts b/lib/vendor-document-list/import-service.ts
index bc384ea2..c7ba041a 100644
--- a/lib/vendor-document-list/import-service.ts
+++ b/lib/vendor-document-list/import-service.ts
@@ -1332,156 +1332,189 @@ class ImportService {
/**
* ๊ฐ€์ ธ์˜ค๊ธฐ ์ƒํƒœ ์กฐํšŒ
*/
- async getImportStatus(
- contractId: number,
- sourceSystem: string = 'DOLCE'
- ): Promise<ImportStatus> {
- try {
- // ๋งˆ์ง€๋ง‰ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ๊ฐ„ ์กฐํšŒ
- const [lastImport] = await db
- .select({
- lastSynced: sql<string>`MAX(${documents.externalSyncedAt})`
- })
- .from(documents)
- .where(and(
- eq(documents.contractId, contractId),
- eq(documents.externalSystemType, sourceSystem)
- ))
+ /**
+ * ๊ฐ€์ ธ์˜ค๊ธฐ ์ƒํƒœ ์กฐํšŒ - ์—๋Ÿฌ ์‹œ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
+ */
+async getImportStatus(
+ contractId: number,
+ sourceSystem: string = 'DOLCE'
+): Promise<ImportStatus> {
+ try {
+ // ๋งˆ์ง€๋ง‰ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ๊ฐ„ ์กฐํšŒ
+ const [lastImport] = await db
+ .select({
+ lastSynced: sql<string>`MAX(${documents.externalSyncedAt})`
+ })
+ .from(documents)
+ .where(and(
+ eq(documents.contractId, contractId),
+ eq(documents.externalSystemType, sourceSystem)
+ ))
- // ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ์™€ ๋ฒค๋” ์ฝ”๋“œ ์กฐํšŒ
- const contractInfo = await this.getContractInfoById(contractId)
+ // ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ์™€ ๋ฒค๋” ์ฝ”๋“œ ์กฐํšŒ
+ const contractInfo = await this.getContractInfoById(contractId)
- if (!contractInfo?.projectCode || !contractInfo?.vendorCode) {
- throw new Error(`Project code or vendor code not found for contract ${contractId}`)
+ // ๐Ÿ”ฅ ๊ณ„์•ฝ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋ณธ ์ƒํƒœ ๋ฐ˜ํ™˜ (์—๋Ÿฌ throw ํ•˜์ง€ ์•Š์Œ)
+ if (!contractInfo?.projectCode || !contractInfo?.vendorCode) {
+ console.warn(`Project code or vendor code not found for contract ${contractId}`)
+ return {
+ lastImportAt: lastImport?.lastSynced ? new Date(lastImport.lastSynced).toISOString() : undefined,
+ availableDocuments: 0,
+ newDocuments: 0,
+ updatedDocuments: 0,
+ availableRevisions: 0,
+ newRevisions: 0,
+ updatedRevisions: 0,
+ availableAttachments: 0,
+ newAttachments: 0,
+ updatedAttachments: 0,
+ importEnabled: false, // ๐Ÿ”ฅ ๊ณ„์•ฝ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฉด import ๋น„ํ™œ์„ฑํ™”
+ error: `Contract ${contractId}์— ๋Œ€ํ•œ ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ ๋˜๋Š” ๋ฒค๋” ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.` // ๐Ÿ”ฅ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€
}
+ }
- let availableDocuments = 0
- let newDocuments = 0
- let updatedDocuments = 0
- let availableRevisions = 0
- let newRevisions = 0
- let updatedRevisions = 0
- let availableAttachments = 0
- let newAttachments = 0
- let updatedAttachments = 0
+ let availableDocuments = 0
+ let newDocuments = 0
+ let updatedDocuments = 0
+ let availableRevisions = 0
+ let newRevisions = 0
+ let updatedRevisions = 0
+ let availableAttachments = 0
+ let newAttachments = 0
+ let updatedAttachments = 0
- try {
- // ๊ฐ drawingKind๋ณ„๋กœ ํ™•์ธ
- const drawingKinds = ['B3', 'B4', 'B5']
+ try {
+ // ๊ฐ drawingKind๋ณ„๋กœ ํ™•์ธ
+ const drawingKinds = ['B3', 'B4', 'B5']
- for (const drawingKind of drawingKinds) {
- try {
- const externalDocs = await this.fetchFromDOLCE(
- contractInfo.projectCode,
- contractInfo.vendorCode,
- drawingKind
- )
- availableDocuments += externalDocs.length
-
- // ์‹ ๊ทœ/์—…๋ฐ์ดํŠธ ๋ฌธ์„œ ์ˆ˜ ๊ณ„์‚ฐ
- for (const externalDoc of externalDocs) {
- const existing = await db
- .select({ id: documents.id, updatedAt: documents.updatedAt })
- .from(documents)
- .where(and(
- eq(documents.contractId, contractId),
- eq(documents.docNumber, externalDoc.DrawingNo)
- ))
- .limit(1)
-
- if (existing.length === 0) {
- newDocuments++
- } else {
- // DOLCE์˜ CreateDt์™€ ๋กœ์ปฌ updatedAt ๋น„๊ต
- if (externalDoc.CreateDt && existing[0].updatedAt) {
- const externalModified = new Date(externalDoc.CreateDt)
- const localModified = new Date(existing[0].updatedAt)
- if (externalModified > localModified) {
- updatedDocuments++
- }
+ for (const drawingKind of drawingKinds) {
+ try {
+ const externalDocs = await this.fetchFromDOLCE(
+ contractInfo.projectCode,
+ contractInfo.vendorCode,
+ drawingKind
+ )
+ availableDocuments += externalDocs.length
+
+ // ์‹ ๊ทœ/์—…๋ฐ์ดํŠธ ๋ฌธ์„œ ์ˆ˜ ๊ณ„์‚ฐ
+ for (const externalDoc of externalDocs) {
+ const existing = await db
+ .select({ id: documents.id, updatedAt: documents.updatedAt })
+ .from(documents)
+ .where(and(
+ eq(documents.contractId, contractId),
+ eq(documents.docNumber, externalDoc.DrawingNo)
+ ))
+ .limit(1)
+
+ if (existing.length === 0) {
+ newDocuments++
+ } else {
+ // DOLCE์˜ CreateDt์™€ ๋กœ์ปฌ updatedAt ๋น„๊ต
+ if (externalDoc.CreateDt && existing[0].updatedAt) {
+ const externalModified = new Date(externalDoc.CreateDt)
+ const localModified = new Date(existing[0].updatedAt)
+ if (externalModified > localModified) {
+ updatedDocuments++
}
}
+ }
- // revisions ๋ฐ attachments ์ƒํƒœ๋„ ํ™•์ธ
- try {
- const detailDocs = await this.fetchDetailFromDOLCE(
- externalDoc.ProjectNo,
- externalDoc.DrawingNo,
- externalDoc.Discipline,
- externalDoc.DrawingKind
- )
- availableRevisions += detailDocs.length
-
- for (const detailDoc of detailDocs) {
- const existingRevision = await db
- .select({ id: revisions.id })
- .from(revisions)
- .where(eq(revisions.registerId, detailDoc.RegisterId))
- .limit(1)
-
- if (existingRevision.length === 0) {
- newRevisions++
- } else {
- updatedRevisions++
- }
+ // revisions ๋ฐ attachments ์ƒํƒœ๋„ ํ™•์ธ
+ try {
+ const detailDocs = await this.fetchDetailFromDOLCE(
+ externalDoc.ProjectNo,
+ externalDoc.DrawingNo,
+ externalDoc.Discipline,
+ externalDoc.DrawingKind
+ )
+ availableRevisions += detailDocs.length
+
+ for (const detailDoc of detailDocs) {
+ const existingRevision = await db
+ .select({ id: revisions.id })
+ .from(revisions)
+ .where(eq(revisions.registerId, detailDoc.RegisterId))
+ .limit(1)
+
+ if (existingRevision.length === 0) {
+ newRevisions++
+ } else {
+ updatedRevisions++
+ }
- // FS Category ๋ฌธ์„œ์˜ ์ฒจ๋ถ€ํŒŒ์ผ ํ™•์ธ
- if (detailDoc.Category === 'FS' && detailDoc.UploadId) {
- try {
- const fileInfos = await this.fetchFileInfoFromDOLCE(detailDoc.UploadId)
- availableAttachments += fileInfos.filter(f => f.UseYn === 'Y').length
-
- for (const fileInfo of fileInfos) {
- if (fileInfo.UseYn !== 'Y') continue
-
- const existingAttachment = await db
- .select({ id: documentAttachments.id })
- .from(documentAttachments)
- .where(eq(documentAttachments.fileId, fileInfo.FileId))
- .limit(1)
-
- if (existingAttachment.length === 0) {
- newAttachments++
- } else {
- updatedAttachments++
- }
+ // FS Category ๋ฌธ์„œ์˜ ์ฒจ๋ถ€ํŒŒ์ผ ํ™•์ธ
+ if (detailDoc.Category === 'FS' && detailDoc.UploadId) {
+ try {
+ const fileInfos = await this.fetchFileInfoFromDOLCE(detailDoc.UploadId)
+ availableAttachments += fileInfos.filter(f => f.UseYn === 'Y').length
+
+ for (const fileInfo of fileInfos) {
+ if (fileInfo.UseYn !== 'Y') continue
+
+ const existingAttachment = await db
+ .select({ id: documentAttachments.id })
+ .from(documentAttachments)
+ .where(eq(documentAttachments.fileId, fileInfo.FileId))
+ .limit(1)
+
+ if (existingAttachment.length === 0) {
+ newAttachments++
+ } else {
+ updatedAttachments++
}
- } catch (error) {
- console.warn(`Failed to check files for ${detailDoc.UploadId}:`, error)
}
+ } catch (error) {
+ console.warn(`Failed to check files for ${detailDoc.UploadId}:`, error)
}
}
- } catch (error) {
- console.warn(`Failed to check revisions for ${externalDoc.DrawingNo}:`, error)
}
+ } catch (error) {
+ console.warn(`Failed to check revisions for ${externalDoc.DrawingNo}:`, error)
}
- } catch (error) {
- console.warn(`Failed to check ${drawingKind} for status:`, error)
}
+ } catch (error) {
+ console.warn(`Failed to check ${drawingKind} for status:`, error)
}
- } catch (error) {
- console.warn(`Failed to fetch external data for status: ${error}`)
}
+ } catch (error) {
+ console.warn(`Failed to fetch external data for status: ${error}`)
+ // ๐Ÿ”ฅ ์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹คํŒจ ์‹œ์—๋„ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
+ }
- return {
- lastImportAt: lastImport?.lastSynced ? new Date(lastImport.lastSynced).toISOString() : undefined,
- availableDocuments,
- newDocuments,
- updatedDocuments,
- availableRevisions,
- newRevisions,
- updatedRevisions,
- availableAttachments,
- newAttachments,
- updatedAttachments,
- importEnabled: this.isImportEnabled(sourceSystem)
- }
+ return {
+ lastImportAt: lastImport?.lastSynced ? new Date(lastImport.lastSynced).toISOString() : undefined,
+ availableDocuments,
+ newDocuments,
+ updatedDocuments,
+ availableRevisions,
+ newRevisions,
+ updatedRevisions,
+ availableAttachments,
+ newAttachments,
+ updatedAttachments,
+ importEnabled: this.isImportEnabled(sourceSystem)
+ }
- } catch (error) {
- console.error('Failed to get import status:', error)
- throw error
+ } catch (error) {
+ // ๐Ÿ”ฅ ์ตœ์ข…์ ์œผ๋กœ ๋ชจ๋“  ์—๋Ÿฌ๋ฅผ catchํ•˜์—ฌ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
+ console.error('Failed to get import status:', error)
+ return {
+ lastImportAt: undefined,
+ availableDocuments: 0,
+ newDocuments: 0,
+ updatedDocuments: 0,
+ availableRevisions: 0,
+ newRevisions: 0,
+ updatedRevisions: 0,
+ availableAttachments: 0,
+ newAttachments: 0,
+ updatedAttachments: 0,
+ importEnabled: false,
+ error: error instanceof Error ? error.message : 'Unknown error occurred'
}
}
+}
/**
* ๊ฐ€์ ธ์˜ค๊ธฐ ํ™œ์„ฑํ™” ์—ฌ๋ถ€ ํ™•์ธ
diff --git a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
index 9c13573c..51c104dc 100644
--- a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
+++ b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
@@ -59,7 +59,7 @@ export function getSimplifiedDocumentColumns({
id: "select",
header: ({ table }) => (
<div className="flex items-center justify-center">
- <span className="text-xs text-gray-500">์„ ํƒ</span>
+ <span className="text-xs text-gray-500">Select</span>
</div>
),
cell: ({ row }) => {
@@ -78,7 +78,7 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "docNumber",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="๋ฌธ์„œ๋ฒˆํ˜ธ" />
+ <DataTableColumnHeaderSimple column={column} title="Document No" />
),
cell: ({ row }) => {
const doc = row.original
@@ -90,7 +90,7 @@ export function getSimplifiedDocumentColumns({
size: 120,
enableResizing: true,
meta: {
- excelHeader: "๋ฌธ์„œ๋ฒˆํ˜ธ"
+ excelHeader: "Document No"
},
},
@@ -98,7 +98,7 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "title",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="๋ฌธ์„œ๋ช…" />
+ <DataTableColumnHeaderSimple column={column} title="Title" />
),
cell: ({ row }) => {
const doc = row.original
@@ -110,7 +110,7 @@ export function getSimplifiedDocumentColumns({
enableResizing: true,
maxSize:300,
meta: {
- excelHeader: "๋ฌธ์„œ๋ช…"
+ excelHeader: "Title"
},
},
@@ -118,7 +118,7 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "projectCode",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="ํ”„๋กœ์ ํŠธ" />
+ <DataTableColumnHeaderSimple column={column} title="Project" />
),
cell: ({ row }) => {
const projectCode = row.original.projectCode
@@ -131,7 +131,7 @@ export function getSimplifiedDocumentColumns({
maxSize:100,
meta: {
- excelHeader: "ํ”„๋กœ์ ํŠธ"
+ excelHeader: "Project"
},
},
@@ -141,7 +141,7 @@ export function getSimplifiedDocumentColumns({
header: ({ table }) => {
// ์ฒซ ๋ฒˆ์งธ ํ–‰์˜ firstStageName์„ ๊ทธ๋ฃน ํ—ค๋”๋กœ ์‚ฌ์šฉ
const firstRow = table.getRowModel().rows[0]?.original
- const stageName = firstRow?.firstStageName || "1์ฐจ ์Šคํ…Œ์ด์ง€"
+ const stageName = firstRow?.firstStageName || "First Stage"
return (
<div className="text-center font-medium text-gray-700">
{stageName}
@@ -152,27 +152,27 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "firstStagePlanDate",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="๊ณ„ํš์ผ" />
+ <DataTableColumnHeaderSimple column={column} title="Planned Date" />
),
cell: ({ row }) => {
return <FirstStagePlanDateCell row={row} />
},
enableResizing: true,
meta: {
- excelHeader: "1์ฐจ ๊ณ„ํš์ผ"
+ excelHeader: "First Planned Date"
},
},
{
accessorKey: "firstStageActualDate",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="์‹ค์ œ์ผ" />
+ <DataTableColumnHeaderSimple column={column} title="Actual Date" />
),
cell: ({ row }) => {
return <FirstStageActualDateCell row={row} />
},
enableResizing: true,
meta: {
- excelHeader: "1์ฐจ ์‹ค์ œ์ผ"
+ excelHeader: "First Actual Date"
},
},
],
@@ -184,7 +184,7 @@ export function getSimplifiedDocumentColumns({
header: ({ table }) => {
// ์ฒซ ๋ฒˆ์งธ ํ–‰์˜ secondStageName์„ ๊ทธ๋ฃน ํ—ค๋”๋กœ ์‚ฌ์šฉ
const firstRow = table.getRowModel().rows[0]?.original
- const stageName = firstRow?.secondStageName || "2์ฐจ ์Šคํ…Œ์ด์ง€"
+ const stageName = firstRow?.secondStageName || "Second Stage"
return (
<div className="text-center font-medium text-gray-700">
{stageName}
@@ -195,27 +195,27 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "secondStagePlanDate",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="๊ณ„ํš์ผ" />
+ <DataTableColumnHeaderSimple column={column} title="Planned Date" />
),
cell: ({ row }) => {
return <SecondStagePlanDateCell row={row} />
},
enableResizing: true,
meta: {
- excelHeader: "2์ฐจ ๊ณ„ํš์ผ"
+ excelHeader: "Second Planned Date"
},
},
{
accessorKey: "secondStageActualDate",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="์‹ค์ œ์ผ" />
+ <DataTableColumnHeaderSimple column={column} title="Actual Date" />
),
cell: ({ row }) => {
return <SecondStageActualDateCell row={row} />
},
enableResizing: true,
meta: {
- excelHeader: "2์ฐจ ์‹ค์ œ์ผ"
+ excelHeader: "Second Actual Date"
},
},
],
@@ -225,7 +225,7 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "attachmentCount",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="ํŒŒ์ผ" />
+ <DataTableColumnHeaderSimple column={column} title="Files" />
),
cell: ({ row }) => {
const count = row.original.attachmentCount || 0
@@ -237,7 +237,7 @@ export function getSimplifiedDocumentColumns({
size: 60,
enableResizing: true,
meta: {
- excelHeader: "์ฒจ๋ถ€ํŒŒ์ผ"
+ excelHeader: "Attachments"
},
},
@@ -245,7 +245,7 @@ export function getSimplifiedDocumentColumns({
{
accessorKey: "updatedAt",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="์—…๋ฐ์ดํŠธ" />
+ <DataTableColumnHeaderSimple column={column} title="Updated" />
),
cell: ({ cell, row }) => {
return (
@@ -254,7 +254,7 @@ export function getSimplifiedDocumentColumns({
},
enableResizing: true,
meta: {
- excelHeader: "์—…๋ฐ์ดํŠธ"
+ excelHeader: "Updated"
},
},
diff --git a/lib/vendor-document-list/ship/enhanced-documents-table.tsx b/lib/vendor-document-list/ship/enhanced-documents-table.tsx
index 2354a9be..9885c027 100644
--- a/lib/vendor-document-list/ship/enhanced-documents-table.tsx
+++ b/lib/vendor-document-list/ship/enhanced-documents-table.tsx
@@ -25,17 +25,17 @@ import { EnhancedDocTableToolbarActions } from "./enhanced-doc-table-toolbar-act
const DRAWING_KIND_INFO = {
B3: {
title: "B3 Vendor",
- description: "Approval โ†’ Work ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋˜๋Š” ์Šน์ธ ์ค‘์‹ฌ ๋„๋ฉด",
+ description: "Approval-focused drawings progressing through Approval โ†’ Work stages",
color: "bg-blue-50 text-blue-700 border-blue-200"
},
B4: {
title: "B4 GTT",
- description: "Pre โ†’ Work ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋˜๋Š” DOLCE ์—ฐ๋™ ๋„๋ฉด",
+ description: "DOLCE-integrated drawings progressing through Pre โ†’ Work stages",
color: "bg-green-50 text-green-700 border-green-200"
},
B5: {
title: "B5 FMEA",
- description: "First โ†’ Second ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋˜๋Š” ์ˆœ์ฐจ์  ๋„๋ฉด",
+ description: "Sequential drawings progressing through First โ†’ Second stages",
color: "bg-purple-50 text-purple-700 border-purple-200"
}
} as const
@@ -79,22 +79,22 @@ export function SimplifiedDocumentsTable({
const advancedFilterFields: DataTableAdvancedFilterField<SimplifiedDocumentsView>[] = [
{
id: "docNumber",
- label: "๋ฌธ์„œ๋ฒˆํ˜ธ",
- type: "text",
- },
- {
- id: "vendorDocNumber",
- label: "๋ฒค๋” ๋ฌธ์„œ๋ฒˆํ˜ธ",
+ label: "Document No",
type: "text",
},
+ // {
+ // id: "vendorDocNumber",
+ // label: "Vendor Document No",
+ // type: "text",
+ // },
{
id: "title",
- label: "๋ฌธ์„œ์ œ๋ชฉ",
+ label: "Document Title",
type: "text",
},
{
id: "drawingKind",
- label: "๋ฌธ์„œ์ข…๋ฅ˜",
+ label: "Document Type",
type: "select",
options: [
{ label: "B3", value: "B3" },
@@ -104,78 +104,78 @@ export function SimplifiedDocumentsTable({
},
{
id: "projectCode",
- label: "ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ",
+ label: "Project Code",
type: "text",
},
{
id: "vendorName",
- label: "๋ฒค๋”๋ช…",
+ label: "Vendor Name",
type: "text",
},
{
id: "vendorCode",
- label: "๋ฒค๋” ์ฝ”๋“œ",
+ label: "Vendor Code",
type: "text",
},
{
id: "pic",
- label: "๋‹ด๋‹น์ž",
+ label: "PIC",
type: "text",
},
{
id: "status",
- label: "๋ฌธ์„œ ์ƒํƒœ",
+ label: "Document Status",
type: "select",
options: [
- { label: "ํ™œ์„ฑ", value: "ACTIVE" },
- { label: "๋น„ํ™œ์„ฑ", value: "INACTIVE" },
- { label: "๋ณด๋ฅ˜", value: "PENDING" },
- { label: "์™„๋ฃŒ", value: "COMPLETED" },
+ { label: "Active", value: "ACTIVE" },
+ { label: "Inactive", value: "INACTIVE" },
+ { label: "Pending", value: "PENDING" },
+ { label: "Completed", value: "COMPLETED" },
],
},
{
id: "firstStageName",
- label: "1์ฐจ ์Šคํ…Œ์ด์ง€",
+ label: "First Stage",
type: "text",
},
{
id: "secondStageName",
- label: "2์ฐจ ์Šคํ…Œ์ด์ง€",
+ label: "Second Stage",
type: "text",
},
{
id: "firstStagePlanDate",
- label: "1์ฐจ ๊ณ„ํš์ผ",
+ label: "First Planned Date",
type: "date",
},
{
id: "firstStageActualDate",
- label: "1์ฐจ ์‹ค์ œ์ผ",
+ label: "First Actual Date",
type: "date",
},
{
id: "secondStagePlanDate",
- label: "2์ฐจ ๊ณ„ํš์ผ",
+ label: "Second Planned Date",
type: "date",
},
{
id: "secondStageActualDate",
- label: "2์ฐจ ์‹ค์ œ์ผ",
+ label: "Second Actual Date",
type: "date",
},
{
id: "issuedDate",
- label: "๋ฐœํ–‰์ผ",
+ label: "Issue Date",
type: "date",
},
{
id: "createdAt",
- label: "์ƒ์„ฑ์ผ",
+ label: "Created Date",
type: "date",
},
{
id: "updatedAt",
- label: "์ˆ˜์ •์ผ",
+ label: "Updated Date",
type: "date",
},
]
@@ -184,32 +184,32 @@ export function SimplifiedDocumentsTable({
const b4FilterFields: DataTableAdvancedFilterField<SimplifiedDocumentsView>[] = [
{
id: "cGbn",
- label: "C ๊ตฌ๋ถ„",
+ label: "C Category",
type: "text",
},
{
id: "dGbn",
- label: "D ๊ตฌ๋ถ„",
+ label: "D Category",
type: "text",
},
{
id: "degreeGbn",
- label: "Degree ๊ตฌ๋ถ„",
+ label: "Degree Category",
type: "text",
},
{
id: "deptGbn",
- label: "Dept ๊ตฌ๋ถ„",
+ label: "Dept Category",
type: "text",
},
{
id: "jGbn",
- label: "J ๊ตฌ๋ถ„",
+ label: "J Category",
type: "text",
},
{
id: "sGbn",
- label: "S ๊ตฌ๋ถ„",
+ label: "S Category",
type: "text",
},
]
@@ -246,17 +246,17 @@ export function SimplifiedDocumentsTable({
{kindInfo && (
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
- <Badge variant="default" className="flex items-center gap-1 text-sm">
+ {/* <Badge variant="default" className="flex items-center gap-1 text-sm">
<FileText className="w-4 h-4" />
{kindInfo.title}
</Badge>
<span className="text-sm text-muted-foreground">
{kindInfo.description}
- </span>
+ </span> */}
</div>
<div className="flex items-center gap-2">
<Badge variant="outline">
- {total}๊ฐœ ๋ฌธ์„œ
+ {total} documents
</Badge>
</div>
</div>
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 d4728d22..de9e63bc 100644
--- a/lib/vendor-document-list/ship/import-from-dolce-button.tsx
+++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
@@ -72,7 +72,7 @@ export function ImportFromDOLCEButton({
setVendorContractIds(contractIds)
} catch (error) {
console.error('Failed to fetch vendor contracts:', error)
- toast.error('๊ณ„์•ฝ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.')
+ toast.error('Failed to fetch contract information.')
} finally {
setLoadingVendorContracts(false)
}
@@ -142,7 +142,7 @@ export function ImportFromDOLCEButton({
} catch (error) {
console.error('Failed to fetch import statuses:', error)
- toast.error('์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์„ค์ •์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.')
+ toast.error('Unable to check status. Please verify project settings.')
} finally {
setStatusLoading(false)
}
@@ -230,16 +230,16 @@ export function ImportFromDOLCEButton({
if (totalResult.success) {
toast.success(
- `DOLCE ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ`,
+ `DOLCE import completed`,
{
- description: `์‹ ๊ทœ ${totalResult.newCount}๊ฑด, ์—…๋ฐ์ดํŠธ ${totalResult.updatedCount}๊ฑด, ๊ฑด๋„ˆ๋œ€ ${totalResult.skippedCount}๊ฑด (${contractIds.length}๊ฐœ ๊ณ„์•ฝ)`
+ description: `New ${totalResult.newCount}, Updated ${totalResult.updatedCount}, Skipped ${totalResult.skippedCount} (${contractIds.length} contracts)`
}
)
} else {
toast.error(
- `DOLCE ๊ฐ€์ ธ์˜ค๊ธฐ ๋ถ€๋ถ„ ์‹คํŒจ`,
+ `DOLCE import partially failed`,
{
- description: '์ผ๋ถ€ ๊ณ„์•ฝ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'
+ description: 'Some contracts failed to import.'
}
)
}
@@ -252,34 +252,34 @@ export function ImportFromDOLCEButton({
setImportProgress(0)
setIsImporting(false)
- toast.error('DOLCE ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ', {
- description: error instanceof Error ? error.message : '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'
+ toast.error('DOLCE import failed', {
+ description: error instanceof Error ? error.message : 'An unknown error occurred.'
})
}
}
const getStatusBadge = () => {
if (loadingVendorContracts) {
- return <Badge variant="secondary">๊ณ„์•ฝ ์ •๋ณด ๋กœ๋”ฉ ์ค‘...</Badge>
+ return <Badge variant="secondary">Loading contract information...</Badge>
}
if (statusLoading) {
- return <Badge variant="secondary">DOLCE ์—ฐ๊ฒฐ ํ™•์ธ ์ค‘...</Badge>
+ return <Badge variant="secondary">Checking DOLCE connection...</Badge>
}
if (importStatusMap.size === 0) {
- return <Badge variant="destructive">DOLCE ์—ฐ๊ฒฐ ์˜ค๋ฅ˜</Badge>
+ return <Badge variant="destructive">DOLCE Connection Error</Badge>
}
if (!totalStats.importEnabled) {
- return <Badge variant="secondary">DOLCE ๊ฐ€์ ธ์˜ค๊ธฐ ๋น„ํ™œ์„ฑํ™”</Badge>
+ return <Badge variant="secondary">DOLCE Import Disabled</Badge>
}
if (totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0) {
return (
<Badge variant="samsung" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ ({contractIds.length}๊ฐœ ๊ณ„์•ฝ)
+ Updates Available ({contractIds.length} contracts)
</Badge>
)
}
@@ -287,7 +287,7 @@ export function ImportFromDOLCEButton({
return (
<Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600">
<CheckCircle className="w-3 h-3" />
- DOLCE์™€ ๋™๊ธฐํ™”๋จ
+ Synchronized with DOLCE
</Badge>
)
}
@@ -316,7 +316,7 @@ export function ImportFromDOLCEButton({
) : (
<Download className="w-4 h-4" />
)}
- <span className="hidden sm:inline">DOLCE์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ</span>
+ <span className="hidden sm:inline">Import from DOLCE</span>
{totalStats.newDocuments + totalStats.updatedDocuments > 0 && (
<Badge
variant="samsung"
@@ -332,9 +332,9 @@ export function ImportFromDOLCEButton({
<PopoverContent className="w-96">
<div className="space-y-4">
<div className="space-y-2">
- <h4 className="font-medium">DOLCE ๊ฐ€์ ธ์˜ค๊ธฐ ์ƒํƒœ</h4>
+ <h4 className="font-medium">DOLCE Import Status</h4>
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">ํ˜„์žฌ ์ƒํƒœ</span>
+ <span className="text-sm text-muted-foreground">Current Status</span>
{getStatusBadge()}
</div>
</div>
@@ -342,15 +342,15 @@ export function ImportFromDOLCEButton({
{/* ๊ณ„์•ฝ ์†Œ์Šค ํ‘œ์‹œ */}
{allDocuments.length === 0 && vendorContractIds.length > 0 && (
<div className="text-xs text-blue-600 bg-blue-50 p-2 rounded">
- ๋ฌธ์„œ๊ฐ€ ์—†์–ด์„œ ์ „์ฒด ๊ณ„์•ฝ์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
+ No documents found, importing from all contracts.
</div>
)}
{/* ๋‹ค์ค‘ ๊ณ„์•ฝ ์ •๋ณด ํ‘œ์‹œ */}
{contractIds.length > 1 && (
<div className="text-sm">
- <div className="text-muted-foreground">๋Œ€์ƒ ๊ณ„์•ฝ</div>
- <div className="font-medium">{contractIds.length}๊ฐœ ๊ณ„์•ฝ</div>
+ <div className="text-muted-foreground">Target Contracts</div>
+ <div className="font-medium">{contractIds.length} contracts</div>
<div className="text-xs text-muted-foreground">
Contract IDs: {contractIds.join(', ')}
</div>
@@ -363,25 +363,25 @@ export function ImportFromDOLCEButton({
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
- <div className="text-muted-foreground">์‹ ๊ทœ ๋ฌธ์„œ</div>
- <div className="font-medium">{totalStats.newDocuments || 0}๊ฑด</div>
+ <div className="text-muted-foreground">New Documents</div>
+ <div className="font-medium">{totalStats.newDocuments || 0}</div>
</div>
<div>
- <div className="text-muted-foreground">์—…๋ฐ์ดํŠธ</div>
- <div className="font-medium">{totalStats.updatedDocuments || 0}๊ฑด</div>
+ <div className="text-muted-foreground">Updates</div>
+ <div className="font-medium">{totalStats.updatedDocuments || 0}</div>
</div>
</div>
<div className="text-sm">
- <div className="text-muted-foreground">DOLCE ์ „์ฒด ๋ฌธ์„œ (B3/B4/B5)</div>
- <div className="font-medium">{totalStats.availableDocuments || 0}๊ฑด</div>
+ <div className="text-muted-foreground">Total DOLCE Documents (B3/B4/B5)</div>
+ <div className="font-medium">{totalStats.availableDocuments || 0}</div>
</div>
{/* ๊ฐ ๊ณ„์•ฝ๋ณ„ ์„ธ๋ถ€ ์ •๋ณด (ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ ๊ฐ€๋Šฅ) */}
{contractIds.length > 1 && (
<details className="text-sm">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
- ๊ณ„์•ฝ๋ณ„ ์„ธ๋ถ€ ์ •๋ณด
+ Details by Contract
</summary>
<div className="mt-2 space-y-2 pl-2 border-l-2 border-muted">
{contractIds.map(contractId => {
@@ -391,10 +391,10 @@ export function ImportFromDOLCEButton({
<div className="font-medium">Contract {contractId}</div>
{status ? (
<div className="text-muted-foreground">
- ์‹ ๊ทœ {status.newDocuments}๊ฑด, ์—…๋ฐ์ดํŠธ {status.updatedDocuments}๊ฑด
+ New {status.newDocuments}, Updates {status.updatedDocuments}
</div>
) : (
- <div className="text-destructive">์ƒํƒœ ํ™•์ธ ์‹คํŒจ</div>
+ <div className="text-destructive">Status check failed</div>
)}
</div>
)
@@ -417,12 +417,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...
+ Importing...
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- ์ง€๊ธˆ ๊ฐ€์ ธ์˜ค๊ธฐ
+ Import Now
</>
)}
</Button>
@@ -448,10 +448,10 @@ export function ImportFromDOLCEButton({
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
- <DialogTitle>DOLCE์—์„œ ๋ฌธ์„œ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ</DialogTitle>
+ <DialogTitle>Import Document List from DOLCE</DialogTitle>
<DialogDescription>
- ์‚ผ์„ฑ์ค‘๊ณต์—… DOLCE ์‹œ์Šคํ…œ์—์„œ ์ตœ์‹  ๋ฌธ์„œ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
- {contractIds.length > 1 && ` (${contractIds.length}๊ฐœ ๊ณ„์•ฝ ๋Œ€์ƒ)`}
+ Import the latest document list from Samsung Heavy Industries DOLCE system.
+ {contractIds.length > 1 && ` (${contractIds.length} contracts targeted)`}
</DialogDescription>
</DialogHeader>
@@ -459,20 +459,20 @@ export function ImportFromDOLCEButton({
{totalStats && (
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between text-sm">
- <span>๊ฐ€์ ธ์˜ฌ ํ•ญ๋ชฉ</span>
+ <span>Items to Import</span>
<span className="font-medium">
- {totalStats.newDocuments + totalStats.updatedDocuments}๊ฑด
+ {totalStats.newDocuments + totalStats.updatedDocuments}
</span>
</div>
<div className="text-xs text-muted-foreground">
- ์‹ ๊ทœ ๋ฌธ์„œ์™€ ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์„œ๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. (B3, B4, B5)
+ Includes new and updated documents (B3, B4, B5).
<br />
- B4 ๋ฌธ์„œ์˜ ๊ฒฝ์šฐ GTTPreDwg, GTTWorkingDwg ์ด์Šˆ ์Šคํ…Œ์ด์ง€๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
+ For B4 documents, GTTPreDwg and GTTWorkingDwg issue stages will be auto-generated.
{contractIds.length > 1 && (
<>
<br />
- {contractIds.length}๊ฐœ ๊ณ„์•ฝ์—์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
+ Will import sequentially from {contractIds.length} contracts.
</>
)}
</div>
@@ -480,7 +480,7 @@ export function ImportFromDOLCEButton({
{isImporting && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
- <span>์ง„ํ–‰๋ฅ </span>
+ <span>Progress</span>
<span>{importProgress}%</span>
</div>
<Progress value={importProgress} className="h-2" />
@@ -495,7 +495,7 @@ export function ImportFromDOLCEButton({
onClick={() => setIsDialogOpen(false)}
disabled={isImporting}
>
- ์ทจ์†Œ
+ Cancel
</Button>
<Button
onClick={handleImport}
@@ -504,12 +504,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...
+ Importing...
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ์ž‘
+ Start Import
</>
)}
</Button>
diff --git a/lib/vendor-document-list/ship/send-to-shi-button.tsx b/lib/vendor-document-list/ship/send-to-shi-button.tsx
index 61893da5..4607c994 100644
--- a/lib/vendor-document-list/ship/send-to-shi-button.tsx
+++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx
@@ -1,8 +1,8 @@
-// components/sync/send-to-shi-button.tsx (๋‹ค์ค‘ ๊ณ„์•ฝ ๋ฒ„์ „)
+// components/sync/send-to-shi-button.tsx (์ตœ์ข… ์™„์„ฑ ๋ฒ„์ „)
"use client"
import * as React from "react"
-import { Send, Loader2, CheckCircle, AlertTriangle, Settings } from "lucide-react"
+import { Send, Loader2, CheckCircle, AlertTriangle, Settings, RefreshCw } from "lucide-react"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
@@ -22,7 +22,9 @@ import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Separator } from "@/components/ui/separator"
import { ScrollArea } from "@/components/ui/scroll-area"
-import { useSyncStatus, useTriggerSync } from "@/hooks/use-sync-status"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+// โœ… ์—…๋ฐ์ดํŠธ๋œ Hook import
+import { useClientSyncStatus, useTriggerSync, syncUtils } from "@/hooks/use-sync-status"
import type { EnhancedDocument } from "@/types/enhanced-documents"
interface SendToSHIButtonProps {
@@ -31,13 +33,6 @@ interface SendToSHIButtonProps {
projectType: "ship" | "plant"
}
-interface ContractSyncStatus {
- contractId: number
- syncStatus: any
- isLoading: boolean
- error: any
-}
-
export function SendToSHIButton({
documents = [],
onSyncComplete,
@@ -49,75 +44,45 @@ export function SendToSHIButton({
const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP"
- // documents์—์„œ contractId ๋ชฉ๋ก ์ถ”์ถœ
+ // ๋ฌธ์„œ์—์„œ ์œ ํšจํ•œ ๊ณ„์•ฝ ID ๋ชฉ๋ก ์ถ”์ถœ
const documentsContractIds = React.useMemo(() => {
- const uniqueIds = [...new Set(documents.map(doc => doc.contractId).filter(Boolean))]
+ const validIds = documents
+ .map(doc => doc.contractId)
+ .filter((id): id is number => typeof id === 'number' && id > 0)
+
+ const uniqueIds = [...new Set(validIds)]
return uniqueIds.sort()
}, [documents])
- // ๊ฐ contract๋ณ„ ๋™๊ธฐํ™” ์ƒํƒœ ์กฐํšŒ
- const contractStatuses = React.useMemo(() => {
- return documentsContractIds.map(contractId => {
- const {
- syncStatus,
- isLoading,
- error,
- refetch
- } = useSyncStatus(contractId, targetSystem)
-
- return {
- contractId,
- syncStatus,
- isLoading,
- error,
- refetch
- }
- })
- }, [documentsContractIds, targetSystem])
-
- const {
- triggerSync,
- isLoading: isSyncing,
- error: syncError
- } = useTriggerSync()
-
- // ์ „์ฒด ํ†ต๊ณ„ ๊ณ„์‚ฐ
- const totalStats = React.useMemo(() => {
- let totalPending = 0
- let totalSynced = 0
- let totalFailed = 0
- let hasError = false
- let isLoading = false
-
- contractStatuses.forEach(({ syncStatus, error, isLoading: loading }) => {
- if (error) hasError = true
- if (loading) isLoading = true
- if (syncStatus) {
- totalPending += syncStatus.pendingChanges || 0
- totalSynced += syncStatus.syncedChanges || 0
- totalFailed += syncStatus.failedChanges || 0
- }
- })
-
- return {
- totalPending,
- totalSynced,
- totalFailed,
- hasError,
- isLoading,
- canSync: totalPending > 0 && !hasError
- }
- }, [contractStatuses])
+ // โœ… ํด๋ผ์ด์–ธํŠธ ์ „์šฉ Hook ์‚ฌ์šฉ (์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ํ˜ธํ™˜)
+ const { contractStatuses, totalStats, refetchAll } = useClientSyncStatus(
+ documentsContractIds,
+ targetSystem
+ )
+
+ const { triggerSync, isLoading: isSyncing, error: syncError } = useTriggerSync()
- // ์—๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ
+ // ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋””๋ฒ„๊น… ์ •๋ณด
React.useEffect(() => {
- if (totalStats.hasError) {
- console.warn('Failed to load sync status for some contracts')
+ if (process.env.NODE_ENV === 'development') {
+ console.log('SendToSHIButton Debug Info:', {
+ documentsContractIds,
+ totalStats,
+ contractStatuses: contractStatuses.map(({ contractId, syncStatus, error }) => ({
+ contractId,
+ pendingChanges: syncStatus?.pendingChanges,
+ hasError: !!error
+ }))
+ })
}
- }, [totalStats.hasError])
+ }, [documentsContractIds, totalStats, contractStatuses])
+ // ๋™๊ธฐํ™” ์‹คํ–‰ ํ•จ์ˆ˜
const handleSync = async () => {
- if (documentsContractIds.length === 0) return
+ if (documentsContractIds.length === 0) {
+ toast.info('๋™๊ธฐํ™”ํ•  ๊ณ„์•ฝ์ด ์—†์Šต๋‹ˆ๋‹ค.')
+ return
+ }
setSyncProgress(0)
let successfulSyncs = 0
@@ -127,9 +92,17 @@ export function SendToSHIButton({
const errors: string[] = []
try {
- const contractsToSync = contractStatuses.filter(
- ({ syncStatus, error }) => !error && syncStatus?.syncEnabled && syncStatus?.pendingChanges > 0
- )
+ // ๋™๊ธฐํ™” ๊ฐ€๋Šฅํ•œ ๊ณ„์•ฝ๋“ค๋งŒ ํ•„ํ„ฐ๋ง
+ const contractsToSync = contractStatuses.filter(({ syncStatus, error }) => {
+ if (error) {
+ console.warn(`Contract ${contractStatuses.find(c => c.error === error)?.contractId} has error:`, error)
+ return false
+ }
+ if (!syncStatus) return false
+ if (!syncStatus.syncEnabled) return false
+ if (syncStatus.pendingChanges <= 0) return false
+ return true
+ })
if (contractsToSync.length === 0) {
toast.info('๋™๊ธฐํ™”ํ•  ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค.')
@@ -137,12 +110,15 @@ export function SendToSHIButton({
return
}
+ console.log(`Starting sync for ${contractsToSync.length} contracts`)
+
// ๊ฐ contract๋ณ„๋กœ ์ˆœ์ฐจ ๋™๊ธฐํ™”
for (let i = 0; i < contractsToSync.length; i++) {
const { contractId } = contractsToSync[i]
setCurrentSyncingContract(contractId)
try {
+ console.log(`Syncing contract ${contractId}...`)
const result = await triggerSync({
contractId,
targetSystem
@@ -151,17 +127,19 @@ export function SendToSHIButton({
if (result?.success) {
successfulSyncs++
totalSuccessCount += result.successCount || 0
+ console.log(`Contract ${contractId} sync successful:`, result)
} else {
failedSyncs++
totalFailureCount += result?.failureCount || 0
- if (result?.errors?.[0]) {
- errors.push(`Contract ${contractId}: ${result.errors[0]}`)
- }
+ const errorMsg = result?.errors?.[0] || result?.message || 'Unknown sync error'
+ errors.push(`Contract ${contractId}: ${errorMsg}`)
+ console.error(`Contract ${contractId} sync failed:`, result)
}
} catch (error) {
failedSyncs++
const errorMessage = error instanceof Error ? error.message : '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜'
errors.push(`Contract ${contractId}: ${errorMessage}`)
+ console.error(`Contract ${contractId} sync exception:`, error)
}
// ์ง„ํ–‰๋ฅ  ์—…๋ฐ์ดํŠธ
@@ -170,6 +148,7 @@ export function SendToSHIButton({
setCurrentSyncingContract(null)
+ // ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ๋ฐ ํ† ์ŠคํŠธ ํ‘œ์‹œ
setTimeout(() => {
setSyncProgress(0)
setIsDialogOpen(false)
@@ -185,7 +164,7 @@ export function SendToSHIButton({
toast.warning(
`๋ถ€๋ถ„ ๋™๊ธฐํ™” ์™„๋ฃŒ: ${successfulSyncs}๊ฐœ ์„ฑ๊ณต, ${failedSyncs}๊ฐœ ์‹คํŒจ`,
{
- description: errors[0] || '์ผ๋ถ€ ๊ณ„์•ฝ ๋™๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'
+ description: errors.slice(0, 3).join(', ') + (errors.length > 3 ? ' ์™ธ ๋”๋ณด๊ธฐ...' : '')
}
)
} else {
@@ -198,7 +177,7 @@ export function SendToSHIButton({
}
// ๋ชจ๋“  contract ์ƒํƒœ ๊ฐฑ์‹ 
- contractStatuses.forEach(({ refetch }) => refetch?.())
+ refetchAll()
onSyncComplete?.()
}, 500)
@@ -206,19 +185,32 @@ export function SendToSHIButton({
setSyncProgress(0)
setCurrentSyncingContract(null)
+ const errorMessage = syncUtils.formatError(error as any)
toast.error('๋™๊ธฐํ™” ์‹คํŒจ', {
- description: error instanceof Error ? error.message : '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'
+ description: errorMessage
})
+ console.error('Sync process failed:', error)
}
}
+ // ๋™๊ธฐํ™” ์ƒํƒœ์— ๋”ฐ๋ฅธ ๋ฑƒ์ง€ ์ƒ์„ฑ
const getSyncStatusBadge = () => {
if (totalStats.isLoading) {
- return <Badge variant="secondary">ํ™•์ธ ์ค‘...</Badge>
+ return (
+ <Badge variant="secondary" className="gap-1">
+ <Loader2 className="w-3 h-3 animate-spin" />
+ ํ™•์ธ ์ค‘...
+ </Badge>
+ )
}
if (totalStats.hasError) {
- return <Badge variant="destructive">์˜ค๋ฅ˜</Badge>
+ return (
+ <Badge variant="destructive" className="gap-1">
+ <AlertTriangle className="w-3 h-3" />
+ ์—ฐ๊ฒฐ ์˜ค๋ฅ˜
+ </Badge>
+ )
}
if (documentsContractIds.length === 0) {
@@ -246,10 +238,6 @@ export function SendToSHIButton({
return <Badge variant="secondary">๋ณ€๊ฒฝ์‚ฌํ•ญ ์—†์Œ</Badge>
}
- const refreshAllStatuses = () => {
- contractStatuses.forEach(({ refetch }) => refetch?.())
- }
-
return (
<>
<Popover>
@@ -258,7 +246,7 @@ export function SendToSHIButton({
<Button
variant="default"
size="sm"
- className="flex items-center bg-blue-600 hover:bg-blue-700"
+ className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700"
disabled={isSyncing || totalStats.isLoading || documentsContractIds.length === 0}
>
{isSyncing ? (
@@ -270,7 +258,7 @@ export function SendToSHIButton({
{totalStats.totalPending > 0 && (
<Badge
variant="destructive"
- className="h-5 w-5 p-0 text-xs flex items-center justify-center"
+ className="h-5 w-5 p-0 text-xs flex items-center justify-center ml-1"
>
{totalStats.totalPending}
</Badge>
@@ -279,33 +267,66 @@ export function SendToSHIButton({
</div>
</PopoverTrigger>
- <PopoverContent className="w-96">
+ <PopoverContent className="w-96" align="end">
<div className="space-y-4">
<div className="space-y-2">
- <h4 className="font-medium">SHI ๋™๊ธฐํ™” ์ƒํƒœ</h4>
+ <div className="flex items-center justify-between">
+ <h4 className="font-medium">SHI ๋™๊ธฐํ™” ์ƒํƒœ</h4>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={refetchAll}
+ disabled={totalStats.isLoading}
+ className="h-6 w-6 p-0"
+ >
+ {totalStats.isLoading ? (
+ <Loader2 className="w-3 h-3 animate-spin" />
+ ) : (
+ <RefreshCw className="w-3 h-3" />
+ )}
+ </Button>
+ </div>
+
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">์ „์ฒด ์ƒํƒœ</span>
{getSyncStatusBadge()}
</div>
+
<div className="text-xs text-muted-foreground">
- {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ ๋Œ€์ƒ
+ {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ ๋Œ€์ƒ โ€ข {targetSystem} ์‹œ์Šคํ…œ
</div>
</div>
+ {/* ์—๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ */}
+ {totalStats.hasError && (
+ <Alert variant="destructive">
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ ์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
+ {process.env.NODE_ENV === 'development' && (
+ <div className="text-xs mt-1 font-mono">
+ Debug: {contractStatuses.filter(({ error }) => error).length}๊ฐœ ๊ณ„์•ฝ์—์„œ ์˜ค๋ฅ˜
+ </div>
+ )}
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* ์ •์ƒ ์ƒํƒœ์ผ ๋•Œ ํ†ต๊ณ„ ํ‘œ์‹œ */}
{!totalStats.hasError && documentsContractIds.length > 0 && (
<div className="space-y-3">
<Separator />
<div className="grid grid-cols-3 gap-4 text-sm">
- <div>
+ <div className="text-center">
<div className="text-muted-foreground">๋Œ€๊ธฐ ์ค‘</div>
- <div className="font-medium">{totalStats.totalPending}๊ฑด</div>
+ <div className="font-medium text-orange-600">{totalStats.totalPending}๊ฑด</div>
</div>
- <div>
+ <div className="text-center">
<div className="text-muted-foreground">๋™๊ธฐํ™”๋จ</div>
- <div className="font-medium">{totalStats.totalSynced}๊ฑด</div>
+ <div className="font-medium text-green-600">{totalStats.totalSynced}๊ฑด</div>
</div>
- <div>
+ <div className="text-center">
<div className="text-muted-foreground">์‹คํŒจ</div>
<div className="font-medium text-red-600">{totalStats.totalFailed}๊ฑด</div>
</div>
@@ -319,17 +340,26 @@ export function SendToSHIButton({
<div className="space-y-2">
{contractStatuses.map(({ contractId, syncStatus, isLoading, error }) => (
<div key={contractId} className="flex items-center justify-between text-xs p-2 rounded border">
- <span>Contract {contractId}</span>
+ <span className="font-medium">Contract {contractId}</span>
{isLoading ? (
- <Badge variant="secondary" className="text-xs">๋กœ๋”ฉ...</Badge>
+ <Badge variant="secondary" className="text-xs">
+ <Loader2 className="w-3 h-3 mr-1 animate-spin" />
+ ๋กœ๋”ฉ...
+ </Badge>
) : error ? (
- <Badge variant="destructive" className="text-xs">์˜ค๋ฅ˜</Badge>
- ) : syncStatus?.pendingChanges > 0 ? (
+ <Badge variant="destructive" className="text-xs">
+ <AlertTriangle className="w-3 h-3 mr-1" />
+ ์˜ค๋ฅ˜
+ </Badge>
+ ) : syncStatus && syncStatus.pendingChanges > 0 ? (
<Badge variant="destructive" className="text-xs">
{syncStatus.pendingChanges}๊ฑด ๋Œ€๊ธฐ
</Badge>
) : (
- <Badge variant="secondary" className="text-xs">๋™๊ธฐํ™”๋จ</Badge>
+ <Badge variant="secondary" className="text-xs">
+ <CheckCircle className="w-3 h-3 mr-1" />
+ ์ตœ์‹ 
+ </Badge>
)}
</div>
))}
@@ -340,28 +370,18 @@ export function SendToSHIButton({
</div>
)}
- {totalStats.hasError && (
- <div className="space-y-2">
- <Separator />
- <div className="text-sm text-red-600">
- <div className="font-medium">์—ฐ๊ฒฐ ์˜ค๋ฅ˜</div>
- <div className="text-xs">์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.</div>
- </div>
- </div>
- )}
-
+ {/* ๊ณ„์•ฝ ์ •๋ณด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ */}
{documentsContractIds.length === 0 && (
- <div className="space-y-2">
- <Separator />
- <div className="text-sm text-muted-foreground">
- <div className="font-medium">๊ณ„์•ฝ ์ •๋ณด ์—†์Œ</div>
- <div className="text-xs">๋™๊ธฐํ™”ํ•  ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>
- </div>
- </div>
+ <Alert>
+ <AlertDescription>
+ ๋™๊ธฐํ™”ํ•  ๋ฌธ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.
+ </AlertDescription>
+ </Alert>
)}
<Separator />
+ {/* ์•ก์…˜ ๋ฒ„ํŠผ๋“ค */}
<div className="flex gap-2">
<Button
onClick={() => setIsDialogOpen(true)}
@@ -385,8 +405,9 @@ export function SendToSHIButton({
<Button
variant="outline"
size="sm"
- onClick={refreshAllStatuses}
+ onClick={refetchAll}
disabled={totalStats.isLoading}
+ className="px-3"
>
{totalStats.isLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
@@ -403,9 +424,12 @@ export function SendToSHIButton({
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
- <DialogTitle>SHI ์‹œ์Šคํ…œ์œผ๋กœ ๋™๊ธฐํ™”</DialogTitle>
+ <DialogTitle className="flex items-center gap-2">
+ <Send className="w-5 h-5" />
+ SHI ์‹œ์Šคํ…œ์œผ๋กœ ๋™๊ธฐํ™”
+ </DialogTitle>
<DialogDescription>
- {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ๋œ ๋ฌธ์„œ ๋ฐ์ดํ„ฐ๋ฅผ SHI ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
+ {documentsContractIds.length}๊ฐœ ๊ณ„์•ฝ์˜ ๋ณ€๊ฒฝ๋œ ๋ฌธ์„œ ๋ฐ์ดํ„ฐ๋ฅผ {targetSystem} ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
</DialogDescription>
</DialogHeader>
@@ -434,7 +458,8 @@ export function SendToSHIButton({
</div>
<Progress value={syncProgress} className="h-2" />
{currentSyncingContract && (
- <div className="text-xs text-muted-foreground">
+ <div className="text-xs text-muted-foreground flex items-center gap-1">
+ <Loader2 className="w-3 h-3 animate-spin" />
ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘: Contract {currentSyncingContract}
</div>
)}
@@ -444,19 +469,20 @@ export function SendToSHIButton({
)}
{totalStats.hasError && (
- <div className="rounded-lg border border-red-200 p-4">
- <div className="text-sm text-red-600">
- ์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
- </div>
- </div>
+ <Alert variant="destructive">
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ ์ผ๋ถ€ ๊ณ„์•ฝ์˜ ๋™๊ธฐํ™” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.
+ </AlertDescription>
+ </Alert>
)}
{documentsContractIds.length === 0 && (
- <div className="rounded-lg border border-yellow-200 p-4">
- <div className="text-sm text-yellow-700">
+ <Alert>
+ <AlertDescription>
๋™๊ธฐํ™”ํ•  ๊ณ„์•ฝ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.
- </div>
- </div>
+ </AlertDescription>
+ </Alert>
)}
<div className="flex justify-end gap-2">