summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-26 18:57:47 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-26 18:57:47 +0900
commit2b5d063ab408a163c016358251192a07a337eaa7 (patch)
tree71b9678169666211dc07663a1b1b9c8c0acf8b91
parent5a370de52d6947d35aa07f9545c4f6a4d83eadd7 (diff)
(김준회) dolce: 다운로드 시각적 피드백 표시 (UX)
-rw-r--r--app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx7
-rw-r--r--lib/dolce/dialogs/detail-drawing-dialog.tsx6
-rw-r--r--lib/dolce/table/file-list-columns.tsx14
3 files changed, 23 insertions, 4 deletions
diff --git a/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx b/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx
index 513cfe1e..55bedb38 100644
--- a/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx
+++ b/app/[lng]/partners/(partners)/dolce-upload-v3/dolce-upload-page-v3.tsx
@@ -85,6 +85,7 @@ export default function DolceUploadPageV3({ searchParams }: DolceUploadPageV3Pro
const [files, setFiles] = useState<FileInfoItem[]>([]);
const [isLoadingDetails, setIsLoadingDetails] = useState(false);
const [, setIsLoadingFiles] = useState(false);
+ const [downloadingFileId, setDownloadingFileId] = useState<string | null>(null);
// 다이얼로그 상태
const [bulkUploadDialogOpen, setBulkUploadDialogOpen] = useState(false);
@@ -311,6 +312,7 @@ export default function DolceUploadPageV3({ searchParams }: DolceUploadPageV3Pro
const handleDownload = async (file: FileInfoItem) => {
try {
+ setDownloadingFileId(file.FileId);
toast.info(t("detailDialog.downloadPreparing"));
const response = await fetch("/api/dolce/download", {
method: "POST",
@@ -337,6 +339,8 @@ export default function DolceUploadPageV3({ searchParams }: DolceUploadPageV3Pro
} catch (error) {
console.error("파일 다운로드 실패:", error);
toast.error(t("detailDialog.downloadError"));
+ } finally {
+ setDownloadingFileId(null);
}
};
@@ -375,7 +379,8 @@ export default function DolceUploadPageV3({ searchParams }: DolceUploadPageV3Pro
const fileColumns = createFileListColumns({
onDownload: handleDownload,
onDelete: handleDeleteFile,
- lng
+ lng,
+ downloadingFileId
});
if (isLoading) {
diff --git a/lib/dolce/dialogs/detail-drawing-dialog.tsx b/lib/dolce/dialogs/detail-drawing-dialog.tsx
index afe4a4c2..99154296 100644
--- a/lib/dolce/dialogs/detail-drawing-dialog.tsx
+++ b/lib/dolce/dialogs/detail-drawing-dialog.tsx
@@ -59,6 +59,7 @@ export function DetailDrawingDialog({
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [editingDetailDrawing, setEditingDetailDrawing] = useState<DetailDwgReceiptItem | null>(null);
const [uploadFilesDialogOpen, setUploadFilesDialogOpen] = useState(false);
+ const [downloadingFileId, setDownloadingFileId] = useState<string | null>(null);
// 상세도면 목록 로드
const loadDetailDrawings = useCallback(async () => {
@@ -126,6 +127,7 @@ export function DetailDrawingDialog({
const handleDownload = async (file: FileInfoItem) => {
try {
+ setDownloadingFileId(file.FileId);
toast.info(t("detailDialog.downloadPreparing"));
// 파일 생성자의 userId를 사용하여 다운로드
@@ -159,6 +161,8 @@ export function DetailDrawingDialog({
} catch (error) {
console.error("파일 다운로드 실패:", error);
toast.error(t("detailDialog.downloadError"));
+ } finally {
+ setDownloadingFileId(null);
}
};
@@ -187,7 +191,7 @@ export function DetailDrawingDialog({
loadFiles();
};
- const fileColumns = createFileListColumns({ onDownload: handleDownload, lng });
+ const fileColumns = createFileListColumns({ onDownload: handleDownload, lng, downloadingFileId });
// RegisterId + UploadId 조합으로 고유 ID 생성
const getDetailDrawingId = (detail: DetailDwgReceiptItem) => {
diff --git a/lib/dolce/table/file-list-columns.tsx b/lib/dolce/table/file-list-columns.tsx
index 38b1f1c9..3018e240 100644
--- a/lib/dolce/table/file-list-columns.tsx
+++ b/lib/dolce/table/file-list-columns.tsx
@@ -3,19 +3,21 @@
import { ColumnDef } from "@tanstack/react-table";
import { FileInfoItem } from "../actions";
import { Button } from "@/components/ui/button";
-import { Download, Trash2 } from "lucide-react";
+import { Download, Trash2, Loader2 } from "lucide-react";
import { formatDolceDateTime } from "../utils/date-formatter";
interface FileListColumnsProps {
onDownload: (file: FileInfoItem) => void;
onDelete?: (file: FileInfoItem) => void;
lng?: string;
+ downloadingFileId?: string | null;
}
export const createFileListColumns = ({
onDownload,
onDelete,
lng = "ko",
+ downloadingFileId,
}: FileListColumnsProps): ColumnDef<FileInfoItem>[] => [
{
accessorKey: "FileSeq",
@@ -64,23 +66,31 @@ export const createFileListColumns = ({
minSize: 160,
cell: ({ row }) => {
const isLocal = row.original.FileServerId === "LOCAL";
+ const isDownloading = downloadingFileId === row.original.FileId;
+
return (
<div className="flex gap-2 items-center justify-center">
<Button
variant="outline"
size="sm"
+ disabled={isDownloading}
onClick={(e) => {
e.stopPropagation();
onDownload(row.original);
}}
>
- <Download className="h-4 w-4 mr-2" />
+ {isDownloading ? (
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
+ ) : (
+ <Download className="h-4 w-4 mr-2" />
+ )}
{lng === "ko" ? "다운로드" : "Download"}
</Button>
{isLocal && onDelete && (
<Button
variant="destructive"
size="sm"
+ disabled={isDownloading}
onClick={(e) => {
e.stopPropagation();
onDelete(row.original);