feat: 파일프로세서 개선 - 안정적인 Excel 파일 처리
- 이전 잘 작동하던 코드 로직을 현재 프로세서에 적용 - LuckyExcel 우선 시도 + SheetJS Fallback 패턴 도입 - CSV, XLS, XLSX 모든 형식에 대한 안정적 처리 - 한글 시트명 정규화 및 워크북 구조 검증 강화 - 복잡한 SheetJS 옵션 단순화로 안정성 향상 - 에러 발생 시 빈 시트 생성으로 앱 중단 방지 - 테스트 환경 및 Cursor 규칙 업데이트 Technical improvements: - convertSheetJSToLuckyExcel 함수로 안정적 데이터 변환 - UTF-8 codepage 설정으로 한글 지원 강화 - validateWorkbook 함수로 방어적 프로그래밍 적용
This commit is contained in:
153
src/components/ui/modal.tsx
Normal file
153
src/components/ui/modal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import React from "react";
|
||||
import { Button } from "./button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "./card";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 모달 컴포넌트
|
||||
* - 배경 클릭으로 닫기
|
||||
* - ESC 키로 닫기
|
||||
* - 접근성 지원 (ARIA)
|
||||
*/
|
||||
export function Modal({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
}: ModalProps) {
|
||||
// ESC 키 이벤트 처리
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "unset";
|
||||
};
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
{/* 배경 오버레이 */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* 모달 콘텐츠 */}
|
||||
<Card className={cn("relative w-full max-w-md", className)}>
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle id="modal-title" className="text-lg font-semibold">
|
||||
{title}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">{children}</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FileErrorModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
errors: Array<{ fileName: string; error: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 파일 업로드 에러 전용 모달
|
||||
*/
|
||||
export function FileErrorModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
errors,
|
||||
}: FileErrorModalProps) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title="파일 업로드 오류"
|
||||
className="max-w-lg"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="h-6 w-6 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.864-.833-2.634 0L3.197 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-gray-900 mb-3">
|
||||
다음 파일들을 업로드할 수 없습니다:
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{errors.map((error, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="p-3 bg-red-50 border border-red-200 rounded-md"
|
||||
>
|
||||
<p className="text-sm font-medium text-red-800">
|
||||
{error.fileName}
|
||||
</p>
|
||||
<p className="text-xs text-red-600 mt-1">{error.error}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-md p-3">
|
||||
<h4 className="text-sm font-medium text-blue-800 mb-2">
|
||||
지원되는 파일 형식:
|
||||
</h4>
|
||||
<ul className="text-xs text-blue-700 space-y-1">
|
||||
<li>• Excel 파일 (.xlsx, .xls)</li>
|
||||
<li>• 최대 파일 크기: 50MB</li>
|
||||
<li>• 한글 파일명 및 시트명 지원</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-2">
|
||||
<Button onClick={onClose} variant="default">
|
||||
확인
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user