Files
sheeteasyAI/src/components/ui/modal.tsx
sheetEasy AI Team 3a8c6af7ea feat: 파일프로세서 개선 - 안정적인 Excel 파일 처리
- 이전 잘 작동하던 코드 로직을 현재 프로세서에 적용
- LuckyExcel 우선 시도 + SheetJS Fallback 패턴 도입
- CSV, XLS, XLSX 모든 형식에 대한 안정적 처리
- 한글 시트명 정규화 및 워크북 구조 검증 강화
- 복잡한 SheetJS 옵션 단순화로 안정성 향상
- 에러 발생 시 빈 시트 생성으로 앱 중단 방지
- 테스트 환경 및 Cursor 규칙 업데이트

Technical improvements:
- convertSheetJSToLuckyExcel 함수로 안정적 데이터 변환
- UTF-8 codepage 설정으로 한글 지원 강화
- validateWorkbook 함수로 방어적 프로그래밍 적용
2025-06-20 14:32:33 +09:00

154 lines
4.1 KiB
TypeScript

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>
);
}