"use client"; import { useState, useTransition, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Label } from "@/components/ui/label"; import { RefreshCw, Search, X, Check, ChevronsUpDown, Upload } from "lucide-react"; import { syncSwpProjectAction, uploadSwpFilesAction, type SwpTableFilters } from "../actions"; import { useToast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; import { useRef } from "react"; import { SwpUploadHelpDialog } from "./swp-help-dialog"; import { SwpUploadResultDialog } from "./swp-upload-result-dialog"; interface SwpTableToolbarProps { filters: SwpTableFilters; onFiltersChange: (filters: SwpTableFilters) => void; projects?: Array<{ PROJ_NO: string; PROJ_NM: string }>; vendorCode?: string; // 벤더가 접속했을 때 고정할 벤더 코드 } export function SwpTableToolbar({ filters, onFiltersChange, projects = [], vendorCode, }: SwpTableToolbarProps) { const [isSyncing, startSync] = useTransition(); const [isUploading, startUpload] = useTransition(); const [localFilters, setLocalFilters] = useState(filters); const { toast } = useToast(); const router = useRouter(); const [projectSearchOpen, setProjectSearchOpen] = useState(false); const [projectSearch, setProjectSearch] = useState(""); const fileInputRef = useRef(null); const [uploadResults, setUploadResults] = useState>([]); const [showResultDialog, setShowResultDialog] = useState(false); // 동기화 핸들러 const handleSync = () => { const projectNo = localFilters.projNo; if (!projectNo) { toast({ variant: "destructive", title: "프로젝트 선택 필요", description: "동기화할 프로젝트를 먼저 선택해주세요.", }); return; } startSync(async () => { try { toast({ title: "동기화 시작", description: `프로젝트 ${projectNo} 동기화를 시작합니다...`, }); const result = await syncSwpProjectAction(projectNo, "V"); if (result.success) { toast({ title: "동기화 완료", description: `문서 ${result.stats.documents.total}개, 파일 ${result.stats.files.total}개 동기화 완료`, }); // 페이지 새로고침 router.refresh(); } else { throw new Error(result.errors.join(", ")); } } catch (error) { console.error("동기화 실패:", error); toast({ variant: "destructive", title: "동기화 실패", description: error instanceof Error ? error.message : "알 수 없는 오류", }); } }); }; /** * 파일 업로드 핸들러 * 1) 네트워크 드라이브에 정해진 규칙대로, 파일이름 기반으로 파일 업로드하기 * 2) 1~N개 파일 받아서, 파일 이름 기준으로 파싱해서 SaveInBoxList API를 통해 업로드 처리 */ const handleUploadFiles = () => { // 프로젝트와 벤더 코드 체크 const projectNo = localFilters.projNo; const vndrCd = vendorCode || localFilters.vndrCd; if (!projectNo) { toast({ variant: "destructive", title: "프로젝트 선택 필요", description: "파일을 업로드할 프로젝트를 먼저 선택해주세요.", }); return; } if (!vndrCd) { toast({ variant: "destructive", title: "업체 코드 입력 필요", description: "파일을 업로드할 업체 코드를 입력해주세요.", }); return; } // 파일 선택 다이얼로그 열기 fileInputRef.current?.click(); }; /** * 파일 선택 핸들러 */ const handleFileChange = async (event: React.ChangeEvent) => { const selectedFiles = event.target.files; if (!selectedFiles || selectedFiles.length === 0) { return; } const projectNo = localFilters.projNo!; const vndrCd = vendorCode || localFilters.vndrCd!; startUpload(async () => { try { toast({ title: "파일 업로드 시작", description: `${selectedFiles.length}개 파일을 업로드합니다...`, }); // 파일을 Buffer로 변환 const fileInfos = await Promise.all( Array.from(selectedFiles).map(async (file) => { const arrayBuffer = await file.arrayBuffer(); return { fileName: file.name, fileBuffer: Buffer.from(arrayBuffer), }; }) ); // 서버 액션 호출 const result = await uploadSwpFilesAction(projectNo, vndrCd, fileInfos); // 결과 저장 및 다이얼로그 표시 setUploadResults(result.details); setShowResultDialog(true); // 성공한 파일이 있으면 페이지 새로고침 const successCount = result.details.filter((d) => d.success).length; if (successCount > 0) { router.refresh(); } } catch (error) { console.error("파일 업로드 실패:", error); // 예외 발생 시에도 결과 다이얼로그 표시 const errorResults = Array.from(selectedFiles).map((file) => ({ fileName: file.name, success: false, error: error instanceof Error ? error.message : "알 수 없는 오류", })); setUploadResults(errorResults); setShowResultDialog(true); } finally { // 파일 입력 초기화 if (fileInputRef.current) { fileInputRef.current.value = ""; } } }); }; // 검색 적용 const handleSearch = () => { onFiltersChange(localFilters); }; // 검색 초기화 const handleReset = () => { const resetFilters: SwpTableFilters = {}; setLocalFilters(resetFilters); onFiltersChange(resetFilters); }; // 프로젝트 필터링 const filteredProjects = useMemo(() => { if (!projectSearch) return projects; const search = projectSearch.toLowerCase(); return projects.filter( (proj) => proj.PROJ_NO.toLowerCase().includes(search) || proj.PROJ_NM.toLowerCase().includes(search) ); }, [projects, projectSearch]); return ( <> {/* 업로드 결과 다이얼로그 */}
{/* 상단 액션 바 */}
{/* 벤더만 파일 업로드 기능 사용 가능 */} {vendorCode && ( <> )}
{/* 검색 필터 */}

검색 필터

{/* 프로젝트 번호 */}
{projects.length > 0 ? (
setProjectSearch(e.target.value)} className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0" />
{filteredProjects.map((proj) => ( ))} {filteredProjects.length === 0 && (
검색 결과가 없습니다.
)}
) : ( setLocalFilters({ ...localFilters, projNo: e.target.value }) } disabled className="bg-muted" /> )}
{/* 문서 번호 */}
setLocalFilters({ ...localFilters, docNo: e.target.value }) } />
{/* 문서 제목 */}
setLocalFilters({ ...localFilters, docTitle: e.target.value }) } />
{/* 패키지 번호 */}
setLocalFilters({ ...localFilters, pkgNo: e.target.value }) } />
{/* 업체 코드 */}
setLocalFilters({ ...localFilters, vndrCd: e.target.value }) } disabled={!!vendorCode} // 벤더 코드가 제공되면 입력 비활성화 className={vendorCode ? "bg-muted" : ""} />
{/* 스테이지 */}
setLocalFilters({ ...localFilters, stage: e.target.value }) } />
); }