"use client"; import React, { useState, useEffect } from "react"; import { PhotoIcon, ClockIcon, CheckCircleIcon, XCircleIcon, SparklesIcon, } from "@heroicons/react/24/outline"; // 처리 작업 상태 interface ProcessingJob { id: string; uploadId: string; status: "pending" | "processing" | "completed" | "failed"; progress: number; error?: string; createdAt: string; startedAt?: string; completedAt?: string; result?: { original: { width: number; height: number; format: string; size: number; }; processed: Array<{ size: string; format: string; width: number; height: number; fileSize: number; url: string; }>; webp?: Array<{ size: string; width: number; height: number; fileSize: number; url: string; }>; }; } interface ImageProcessorProps { uploadId: string; onProcessingComplete?: (result: ProcessingJob["result"]) => void; className?: string; } export function ImageProcessor({ uploadId, onProcessingComplete, className = "", }: ImageProcessorProps) { const [job, setJob] = useState(null); const [error, setError] = useState(""); // 처리 상태 폴링 useEffect(() => { if (!uploadId) return; const checkStatus = async () => { try { const response = await fetch(`/api/media/process?uploadId=${uploadId}`); const data = await response.json(); if (data.success && data.job) { setJob(data.job); // 처리 완료 시 콜백 호출 if ( data.job.status === "completed" && data.job.result && onProcessingComplete ) { onProcessingComplete(data.job.result); } } else if (!response.ok) { setError(data.error || "Failed to get processing status"); } } catch (err) { setError("Failed to check processing status"); console.error("Processing status check failed:", err); } }; // 초기 확인 checkStatus(); // 처리 중인 경우 주기적으로 확인 const interval = setInterval(() => { if (job?.status === "processing" || job?.status === "pending") { checkStatus(); } }, 2000); return () => clearInterval(interval); }, [uploadId, job?.status, onProcessingComplete]); const getStatusIcon = (status: ProcessingJob["status"]) => { switch (status) { case "pending": return ; case "processing": return ; case "completed": return ; case "failed": return ; default: return ; } }; const getStatusText = (status: ProcessingJob["status"]) => { switch (status) { case "pending": return "대기 중..."; case "processing": return "처리 중..."; case "completed": return "처리 완료"; case "failed": return "처리 실패"; default: return "알 수 없음"; } }; const formatFileSize = (bytes: number): string => { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; }; const calculateSavings = (original: number, processed: number): number => { return Math.round(((original - processed) / original) * 100); }; if (error) { return (
{error}
); } if (!job) { return (
처리 상태 확인 중...
); } return (
{/* 상태 헤더 */}
{getStatusIcon(job.status)} {getStatusText(job.status)}
{job.progress}%
{/* 진행률 바 */} {(job.status === "processing" || job.status === "pending") && (
)} {/* 에러 메시지 */} {job.status === "failed" && job.error && (

{job.error}

)} {/* 처리 결과 */} {job.status === "completed" && job.result && (
{/* 원본 정보 */}

원본 이미지

크기: {job.result.original.width} × {job.result.original.height} px
포맷: {job.result.original.format.toUpperCase()}
파일 크기: {formatFileSize(job.result.original.size)}
{/* 처리된 이미지들 */}

최적화된 이미지

{job.result.processed.map((processed, index) => (
{processed.size} ({processed.width} × {processed.height} px)
{formatFileSize(processed.fileSize)} •{" "} {processed.format.toUpperCase()}
- {calculateSavings( job.result!.original.size, processed.fileSize )} %
))}
{/* WebP 이미지들 */} {job.result.webp && job.result.webp.length > 0 && (

WebP 이미지

{job.result.webp.map((webp, index) => (
{webp.size} ({webp.width} × {webp.height}px)
{formatFileSize(webp.fileSize)} • WebP
추가 최적화
))}
)} {/* 처리 시간 */} {job.startedAt && job.completedAt && (
처리 시간:{" "} {Math.round( (new Date(job.completedAt).getTime() - new Date(job.startedAt).getTime()) / 1000 )} 초
)}
)}
); }