summaryrefslogtreecommitdiff
path: root/lib/rfq-last/attachment/vendor-response-table.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-15 14:41:01 +0000
commit4ee8b24cfadf47452807fa2af801385ed60ab47c (patch)
treee1d1fb029f0cf5519c517494bf9a545505c35700 /lib/rfq-last/attachment/vendor-response-table.tsx
parent265859d691a01cdcaaf9154f93c38765bc34df06 (diff)
(대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth
Diffstat (limited to 'lib/rfq-last/attachment/vendor-response-table.tsx')
-rw-r--r--lib/rfq-last/attachment/vendor-response-table.tsx387
1 files changed, 299 insertions, 88 deletions
diff --git a/lib/rfq-last/attachment/vendor-response-table.tsx b/lib/rfq-last/attachment/vendor-response-table.tsx
index 6e1a02c8..f9388752 100644
--- a/lib/rfq-last/attachment/vendor-response-table.tsx
+++ b/lib/rfq-last/attachment/vendor-response-table.tsx
@@ -17,7 +17,7 @@ import {
FileCode,
Building2,
Calendar,
- AlertCircle
+ AlertCircle, X
} from "lucide-react";
import { format, formatDistanceToNow, isValid, isBefore, isAfter } from "date-fns";
import { ko } from "date-fns/locale";
@@ -46,6 +46,22 @@ import { cn } from "@/lib/utils";
import { getRfqVendorAttachments } from "@/lib/rfq-last/service";
import { downloadFile } from "@/lib/file-download";
import { toast } from "sonner";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
// 타입 정의
interface VendorAttachment {
@@ -138,24 +154,79 @@ export function VendorResponseTable({
const [isRefreshing, setIsRefreshing] = React.useState(false);
const [selectedRows, setSelectedRows] = React.useState<VendorAttachment[]>([]);
- // 데이터 새로고침
- const handleRefresh = React.useCallback(async () => {
- setIsRefreshing(true);
+
+
+ const [isUpdating, setIsUpdating] = React.useState(false);
+ const [showTypeDialog, setShowTypeDialog] = React.useState(false);
+ const [selectedType, setSelectedType] = React.useState<"구매" | "설계" | "">("");
+ console.log(data,"data")
+
+ const [selectedVendor, setSelectedVendor] = React.useState<string | null>(null);
+
+ const filteredData = React.useMemo(() => {
+ if (!selectedVendor) return data;
+ return data.filter(item => item.vendorName === selectedVendor);
+ }, [data, selectedVendor]);
+
+
+
+ // 데이터 새로고침
+ const handleRefresh = React.useCallback(async () => {
+ setIsRefreshing(true);
+ try {
+ const result = await getRfqVendorAttachments(rfqId);
+ if (result.vendorSuccess && result.vendorData) {
+ setData(result.vendorData);
+ toast.success("데이터를 새로고침했습니다.");
+ } else {
+ toast.error("데이터를 불러오는데 실패했습니다.");
+ }
+ } catch (error) {
+ console.error("Refresh error:", error);
+ toast.error("새로고침 중 오류가 발생했습니다.");
+ } finally {
+ setIsRefreshing(false);
+ }
+ }, [rfqId]);
+
+ const toggleVendorFilter = (vendor: string) => {
+ if (selectedVendor === vendor) {
+ setSelectedVendor(null); // 이미 선택된 벤더를 다시 클릭하면 필터 해제
+ } else {
+ setSelectedVendor(vendor);
+ // 필터 변경 시 선택 초기화 (옵션)
+ setSelectedRows([]);
+ }
+ };
+
+ // 문서 유형 일괄 변경
+ const handleBulkTypeChange = React.useCallback(async () => {
+ if (!selectedType || selectedRows.length === 0) return;
+
+ setIsUpdating(true);
try {
- const result = await getRfqVendorAttachments(rfqId);
- if (result.success && result.data) {
- setData(result.data);
- toast.success("데이터를 새로고침했습니다.");
+ const ids = selectedRows.map(row => row.id);
+ const result = await updateAttachmentTypes(ids, selectedType as "구매" | "설계");
+
+ if (result.success) {
+ toast.success(result.message);
+ // 데이터 새로고침
+ await handleRefresh();
+ // 선택 초기화
+ setSelectedRows([]);
+ setShowTypeDialog(false);
+ setSelectedType("");
} else {
- toast.error("데이터를 불러오는데 실패했습니다.");
+ toast.error(result.message);
}
} catch (error) {
- console.error("Refresh error:", error);
- toast.error("새로고침 중 오류가 발생했습니다.");
+ toast.error("문서 유형 변경 중 오류가 발생했습니다.");
} finally {
- setIsRefreshing(false);
+ setIsUpdating(false);
}
- }, [rfqId]);
+ }, [selectedType, selectedRows, handleRefresh]);
+
+
// 액션 처리
const handleAction = React.useCallback(async (action: DataTableRowAction<VendorAttachment>) => {
@@ -282,56 +353,56 @@ export function VendorResponseTable({
},
size: 300,
},
- {
- accessorKey: "description",
- header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="설명" />,
- cell: ({ row }) => (
- <div className="max-w-[200px] truncate" title={row.original.description || ""}>
- {row.original.description || "-"}
- </div>
- ),
- size: 200,
- },
- {
- accessorKey: "validTo",
- header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="유효기간" />,
- cell: ({ row }) => {
- const { validFrom, validTo } = row.original;
- const validity = checkValidity(validTo);
+ // {
+ // accessorKey: "description",
+ // header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="설명" />,
+ // cell: ({ row }) => (
+ // <div className="max-w-[200px] truncate" title={row.original.description || ""}>
+ // {row.original.description || "-"}
+ // </div>
+ // ),
+ // size: 200,
+ // },
+ // {
+ // accessorKey: "validTo",
+ // header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="유효기간" />,
+ // cell: ({ row }) => {
+ // const { validFrom, validTo } = row.original;
+ // const validity = checkValidity(validTo);
- if (!validTo) return <span className="text-muted-foreground">-</span>;
+ // if (!validTo) return <span className="text-muted-foreground">-</span>;
- return (
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger asChild>
- <div className="flex items-center gap-2">
- {validity === "expired" && (
- <AlertCircle className="h-4 w-4 text-red-500" />
- )}
- {validity === "expiring-soon" && (
- <AlertCircle className="h-4 w-4 text-yellow-500" />
- )}
- <span className={cn(
- "text-sm",
- validity === "expired" && "text-red-500",
- validity === "expiring-soon" && "text-yellow-500"
- )}>
- {format(new Date(validTo), "yyyy-MM-dd")}
- </span>
- </div>
- </TooltipTrigger>
- <TooltipContent>
- <p>유효기간: {validFrom ? format(new Date(validFrom), "yyyy-MM-dd") : "?"} ~ {format(new Date(validTo), "yyyy-MM-dd")}</p>
- {validity === "expired" && <p className="text-red-500">만료됨</p>}
- {validity === "expiring-soon" && <p className="text-yellow-500">곧 만료 예정</p>}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- );
- },
- size: 120,
- },
+ // return (
+ // <TooltipProvider>
+ // <Tooltip>
+ // <TooltipTrigger asChild>
+ // <div className="flex items-center gap-2">
+ // {validity === "expired" && (
+ // <AlertCircle className="h-4 w-4 text-red-500" />
+ // )}
+ // {validity === "expiring-soon" && (
+ // <AlertCircle className="h-4 w-4 text-yellow-500" />
+ // )}
+ // <span className={cn(
+ // "text-sm",
+ // validity === "expired" && "text-red-500",
+ // validity === "expiring-soon" && "text-yellow-500"
+ // )}>
+ // {format(new Date(validTo), "yyyy-MM-dd")}
+ // </span>
+ // </div>
+ // </TooltipTrigger>
+ // <TooltipContent>
+ // <p>유효기간: {validFrom ? format(new Date(validFrom), "yyyy-MM-dd") : "?"} ~ {format(new Date(validTo), "yyyy-MM-dd")}</p>
+ // {validity === "expired" && <p className="text-red-500">만료됨</p>}
+ // {validity === "expiring-soon" && <p className="text-yellow-500">곧 만료 예정</p>}
+ // </TooltipContent>
+ // </Tooltip>
+ // </TooltipProvider>
+ // );
+ // },
+ // size: 120,
+ // },
{
accessorKey: "responseStatus",
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="응답 상태" />,
@@ -424,13 +495,13 @@ export function VendorResponseTable({
label: "문서 유형",
type: "select",
options: [
- { label: "견적서", value: "견적서" },
- { label: "기술제안서", value: "기술제안서" },
- { label: "인증서", value: "인증서" },
- { label: "카탈로그", value: "카탈로그" },
- { label: "도면", value: "도면" },
- { label: "테스트성적서", value: "테스트성적서" },
- { label: "기타", value: "기타" },
+ { label: "구매", value: "구매" },
+ { label: "설계", value: "설계" },
+ // { label: "인증서", value: "인증서" },
+ // { label: "카탈로그", value: "카탈로그" },
+ // { label: "도면", value: "도면" },
+ // { label: "테스트성적서", value: "테스트성적서" },
+ // { label: "기타", value: "기타" },
]
},
{ id: "documentNo", label: "문서번호", type: "text" },
@@ -448,23 +519,35 @@ export function VendorResponseTable({
{ label: "취소", value: "취소" },
]
},
- { id: "validFrom", label: "유효시작일", type: "date" },
- { id: "validTo", label: "유효종료일", type: "date" },
+ // { id: "validFrom", label: "유효시작일", type: "date" },
+ // { id: "validTo", label: "유효종료일", type: "date" },
{ id: "uploadedAt", label: "업로드일", type: "date" },
];
- // 추가 액션 버튼들
+ // 추가 액션 버튼들 수정
const additionalActions = React.useMemo(() => (
<div className="flex items-center gap-2">
{selectedRows.length > 0 && (
- <Button
- variant="outline"
- size="sm"
- onClick={handleBulkDownload}
- >
- <Download className="h-4 w-4 mr-2" />
- 다운로드 ({selectedRows.length})
- </Button>
+ <>
+ {/* 문서 유형 변경 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => setShowTypeDialog(true)}
+ >
+ <FileText className="h-4 w-4 mr-2" />
+ 유형 변경 ({selectedRows.length})
+ </Button>
+
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleBulkDownload}
+ >
+ <Download className="h-4 w-4 mr-2" />
+ 다운로드 ({selectedRows.length})
+ </Button>
+ </>
)}
<Button
variant="outline"
@@ -476,7 +559,7 @@ export function VendorResponseTable({
새로고침
</Button>
</div>
- ), [selectedRows, isRefreshing, handleBulkDownload, handleRefresh]);
+ ), [selectedRows, isRefreshing, handleBulkDownload, handleRefresh])
// 벤더별 그룹 카운트
const vendorCounts = React.useMemo(() => {
@@ -490,18 +573,71 @@ export function VendorResponseTable({
return (
<div className={cn("w-full space-y-4")}>
- {/* 벤더별 요약 정보 */}
- <div className="flex gap-2 flex-wrap">
- {Array.from(vendorCounts.entries()).map(([vendor, count]) => (
- <Badge key={vendor} variant="secondary">
- {vendor}: {count}
- </Badge>
- ))}
+ {/* 벤더 필터 섹션 */}
+ <div className="space-y-2">
+ {/* 필터 헤더 */}
+ <div className="flex items-center justify-between">
+ <span className="text-sm font-medium text-muted-foreground">
+ 벤더별 필터
+ </span>
+ {selectedVendor && (
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => setSelectedVendor(null)}
+ className="h-7 px-2 text-xs"
+ >
+ <X className="h-3 w-3 mr-1" />
+ 필터 초기화
+ </Button>
+ )}
+ </div>
+
+ {/* 벤더 버튼들 */}
+ <div className="flex gap-2 flex-wrap">
+ {/* 전체 보기 버튼 */}
+ <Button
+ variant={selectedVendor === null ? "default" : "outline"}
+ size="sm"
+ onClick={() => setSelectedVendor(null)}
+ className="h-7"
+ >
+ <span className="text-xs">
+ 전체 ({data.length})
+ </span>
+ </Button>
+
+ {/* 각 벤더별 버튼 */}
+ {Array.from(vendorCounts.entries()).map(([vendor, count]) => (
+ <Button
+ key={vendor}
+ variant={selectedVendor === vendor ? "default" : "outline"}
+ size="sm"
+ onClick={() => toggleVendorFilter(vendor)}
+ className="h-7"
+ >
+ <Building2 className="h-3 w-3 mr-1" />
+ <span className="text-xs">
+ {vendor} ({count})
+ </span>
+ </Button>
+ ))}
+ </div>
+
+ {/* 현재 필터 상태 표시 */}
+ {selectedVendor && (
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
+ <AlertCircle className="h-3 w-3" />
+ <span>
+ "{selectedVendor}" 벤더의 {filteredData.length}개 항목만 표시 중
+ </span>
+ </div>
+ )}
</div>
<ClientDataTable
columns={columns}
- data={data}
+ data={filteredData} // 필터링된 데이터 사용
advancedFilterFields={advancedFilterFields}
autoSizeColumns={true}
compact={true}
@@ -514,6 +650,81 @@ export function VendorResponseTable({
>
{additionalActions}
</ClientDataTable>
+
+ {/* 문서 유형 변경 다이얼로그 */}
+ <Dialog open={showTypeDialog} onOpenChange={setShowTypeDialog}>
+ <DialogContent className="sm:max-w-[425px]">
+ <DialogHeader>
+ <DialogTitle>문서 유형 변경</DialogTitle>
+ <DialogDescription>
+ 선택한 {selectedRows.length}개 항목의 문서 유형을 변경합니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <label htmlFor="type" className="text-right">
+ 문서 유형
+ </label>
+ <Select
+ value={selectedType}
+ onValueChange={(value) => setSelectedType(value as "구매" | "설계")}
+ >
+ <SelectTrigger className="col-span-3">
+ <SelectValue placeholder="문서 유형 선택" />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value="구매">구매</SelectItem>
+ <SelectItem value="설계">설계</SelectItem>
+ </SelectContent>
+ </Select>
+ </div>
+
+ {/* 현재 선택된 항목들의 정보 표시 */}
+ <div className="text-sm text-muted-foreground">
+ <p>변경될 항목:</p>
+ <ul className="mt-2 max-h-32 overflow-y-auto space-y-1">
+ {selectedRows.slice(0, 5).map((row) => (
+ <li key={row.id} className="text-xs">
+ • {row.vendorName} - {row.originalFileName}
+ </li>
+ ))}
+ {selectedRows.length > 5 && (
+ <li className="text-xs italic">
+ ... 외 {selectedRows.length - 5}개
+ </li>
+ )}
+ </ul>
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => {
+ setShowTypeDialog(false);
+ setSelectedType("");
+ }}
+ disabled={isUpdating}
+ >
+ 취소
+ </Button>
+ <Button
+ onClick={handleBulkTypeChange}
+ disabled={!selectedType || isUpdating}
+ >
+ {isUpdating ? (
+ <>
+ <RefreshCw className="mr-2 h-4 w-4 animate-spin" />
+ 변경 중...
+ </>
+ ) : (
+ "변경"
+ )}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
</div>
);
} \ No newline at end of file