feature/screen-management #340

Merged
kjs merged 11 commits from feature/screen-management into main 2026-01-08 12:33:16 +09:00
1 changed files with 235 additions and 233 deletions
Showing only changes of commit 83eb92cb27 - Show all commits

View File

@ -18,7 +18,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { toast } from "sonner";
import {
Upload,
@ -62,29 +61,23 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
}) => {
const [currentStep, setCurrentStep] = useState(1);
// 1단계: 파일 선택
// 1단계: 파일 선택 & 미리보기
const [file, setFile] = useState<File | null>(null);
const [sheetNames, setSheetNames] = useState<string[]>([]);
const [selectedSheet, setSelectedSheet] = useState<string>("");
const [isDragOver, setIsDragOver] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// 2단계: 범위 지정
// (더 이상 사용하지 않는 상태들 - 3단계로 이동)
// 3단계: 컬럼 매핑 + 매핑 템플릿 자동 적용
const [isAutoMappingLoaded, setIsAutoMappingLoaded] = useState(false);
const [detectedRange, setDetectedRange] = useState<string>("");
const [previewData, setPreviewData] = useState<Record<string, any>[]>([]);
const [allData, setAllData] = useState<Record<string, any>[]>([]);
const [displayData, setDisplayData] = useState<Record<string, any>[]>([]);
// 3단계: 컬럼 매핑
// 2단계: 컬럼 매핑 + 매핑 템플릿 자동 적용
const [isAutoMappingLoaded, setIsAutoMappingLoaded] = useState(false);
const [excelColumns, setExcelColumns] = useState<string[]>([]);
const [systemColumns, setSystemColumns] = useState<TableColumn[]>([]);
const [columnMappings, setColumnMappings] = useState<ColumnMapping[]>([]);
// 4단계: 확인
// 3단계: 확인
const [isUploading, setIsUploading] = useState(false);
// 파일 선택 핸들러
@ -160,7 +153,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
try {
const data = await importFromExcel(file, sheetName);
setAllData(data);
setDisplayData(data); // 전체 데이터를 미리보기에 표시 (스크롤 가능)
setDisplayData(data);
if (data.length > 0) {
const columns = Object.keys(data[0]);
@ -224,30 +217,30 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
}
};
// 테이블 스키마 가져오기
// 테이블 스키마 가져오기 (2단계 진입 시)
useEffect(() => {
if (currentStep === 3 && tableName) {
if (currentStep === 2 && tableName) {
loadTableSchema();
}
}, [currentStep, tableName]);
// 테이블 생성 시 자동 생성되는 시스템 컬럼 (매핑에서 제외)
const AUTO_GENERATED_COLUMNS = [
"id", // ID
"created_date", // 생성일시
"updated_date", // 수정일시
"writer", // 작성자
"company_code", // 회사코드
"id",
"created_date",
"updated_date",
"writer",
"company_code",
];
const loadTableSchema = async () => {
try {
console.log("🔍 테이블 스키마 로드 시작:", { tableName });
const response = await getTableSchema(tableName);
console.log("📊 테이블 스키마 응답:", response);
if (response.success && response.data) {
// 자동 생성 컬럼 제외
const filteredColumns = response.data.columns.filter(
@ -259,19 +252,19 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
// 기존 매핑 템플릿 조회
console.log("🔍 매핑 템플릿 조회 중...", { tableName, excelColumns });
const mappingResponse = await findMappingByColumns(tableName, excelColumns);
if (mappingResponse.success && mappingResponse.data) {
// 저장된 매핑 템플릿이 있으면 자동 적용
console.log("✅ 기존 매핑 템플릿 발견:", mappingResponse.data);
const savedMappings = mappingResponse.data.columnMappings;
const appliedMappings: ColumnMapping[] = excelColumns.map((col) => ({
excelColumn: col,
systemColumn: savedMappings[col] || null,
}));
setColumnMappings(appliedMappings);
setIsAutoMappingLoaded(true);
const matchedCount = appliedMappings.filter((m) => m.systemColumn).length;
toast.success(`이전 매핑 템플릿이 적용되었습니다. (${matchedCount}개 컬럼)`);
} else {
@ -297,10 +290,11 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
const handleAutoMapping = () => {
const newMappings = excelColumns.map((excelCol) => {
const normalizedExcelCol = excelCol.toLowerCase().trim();
// 1. 먼저 라벨로 매칭 시도
let matchedSystemCol = systemColumns.find(
(sysCol) => sysCol.label && sysCol.label.toLowerCase().trim() === normalizedExcelCol
(sysCol) =>
sysCol.label && sysCol.label.toLowerCase().trim() === normalizedExcelCol
);
// 2. 라벨로 매칭되지 않으면 컬럼명으로 매칭 시도
@ -325,9 +319,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
const handleMappingChange = (excelColumn: string, systemColumn: string | null) => {
setColumnMappings((prev) =>
prev.map((mapping) =>
mapping.excelColumn === excelColumn
? { ...mapping, systemColumn }
: mapping
mapping.excelColumn === excelColumn ? { ...mapping, systemColumn } : mapping
)
);
};
@ -339,12 +331,12 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
return;
}
if (currentStep === 2 && displayData.length === 0) {
if (currentStep === 1 && displayData.length === 0) {
toast.error("데이터가 없습니다.");
return;
}
setCurrentStep((prev) => Math.min(prev + 1, 4));
setCurrentStep((prev) => Math.min(prev + 1, 3));
};
// 이전 단계
@ -362,7 +354,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
setIsUploading(true);
try {
// allData를 사용하여 전체 데이터 업로드 (displayData는 미리보기용 10개만)
// allData를 사용하여 전체 데이터 업로드
const mappedData = allData.map((row) => {
const mappedRow: Record<string, any> = {};
columnMappings.forEach((mapping) => {
@ -376,7 +368,6 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
// 빈 행 필터링: 모든 값이 비어있거나 undefined/null인 행 제외
const filteredData = mappedData.filter((row) => {
const values = Object.values(row);
// 하나라도 유효한 값이 있는지 확인
return values.some((value) => {
if (value === undefined || value === null) return false;
if (typeof value === "string" && value.trim() === "") return false;
@ -384,7 +375,9 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
});
});
console.log(`📊 엑셀 업로드: 전체 ${mappedData.length}행 중 유효한 ${filteredData.length}`);
console.log(
`📊 엑셀 업로드: 전체 ${mappedData.length}행 중 유효한 ${filteredData.length}`
);
let successCount = 0;
let failCount = 0;
@ -416,10 +409,18 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
columnMappings.forEach((mapping) => {
mappingsToSave[mapping.excelColumn] = mapping.systemColumn;
});
console.log("💾 매핑 템플릿 저장 중...", { tableName, excelColumns, mappingsToSave });
const saveResult = await saveMappingTemplate(tableName, excelColumns, mappingsToSave);
console.log("💾 매핑 템플릿 저장 중...", {
tableName,
excelColumns,
mappingsToSave,
});
const saveResult = await saveMappingTemplate(
tableName,
excelColumns,
mappingsToSave
);
if (saveResult.success) {
console.log("✅ 매핑 템플릿 저장 완료:", saveResult.data);
} else {
@ -427,7 +428,6 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
}
} catch (error) {
console.warn("⚠️ 매핑 템플릿 저장 중 오류:", error);
// 매핑 템플릿 저장 실패해도 업로드는 성공이므로 에러 표시 안함
}
onSuccess?.();
@ -451,7 +451,6 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
setSelectedSheet("");
setIsAutoMappingLoaded(false);
setDetectedRange("");
setPreviewData([]);
setAllData([]);
setDisplayData([]);
setExcelColumns([]);
@ -479,17 +478,16 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
</DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
. .
.
</DialogDescription>
</DialogHeader>
{/* 스텝 인디케이터 */}
{/* 스텝 인디케이터 (3단계) */}
<div className="flex items-center justify-between">
{[
{ num: 1, label: "파일 선택" },
{ num: 2, label: "범위 지정" },
{ num: 3, label: "컬럼 매핑" },
{ num: 4, label: "확인" },
{ num: 2, label: "컬럼 매핑" },
{ num: 3, label: "확인" },
].map((step, index) => (
<React.Fragment key={step.num}>
<div className="flex flex-col items-center gap-1">
@ -512,15 +510,13 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<span
className={cn(
"text-[10px] font-medium sm:text-xs",
currentStep === step.num
? "text-primary"
: "text-muted-foreground"
currentStep === step.num ? "text-primary" : "text-muted-foreground"
)}
>
{step.label}
</span>
</div>
{index < 3 && (
{index < 2 && (
<div
className={cn(
"h-0.5 flex-1 transition-colors",
@ -534,21 +530,21 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
{/* 스텝별 컨텐츠 */}
<div className="max-h-[calc(95vh-200px)] space-y-4 overflow-y-auto">
{/* 1단계: 파일 선택 */}
{/* 1단계: 파일 선택 & 미리보기 (통합) */}
{currentStep === 1 && (
<div className="space-y-4">
{/* 파일 선택 영역 */}
<div>
<Label htmlFor="file-upload" className="text-xs sm:text-sm">
*
</Label>
{/* 드래그 앤 드롭 영역 */}
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
className={cn(
"mt-2 flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-6 transition-colors",
"mt-2 flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed p-4 transition-colors",
isDragOver
? "border-primary bg-primary/5"
: file
@ -557,24 +553,32 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
)}
>
{file ? (
<>
<FileSpreadsheet className="mb-2 h-10 w-10 text-green-600" />
<p className="text-sm font-medium text-green-700">{file.name}</p>
<p className="mt-1 text-xs text-muted-foreground">
</p>
</>
<div className="flex items-center gap-3">
<FileSpreadsheet className="h-8 w-8 text-green-600" />
<div>
<p className="text-sm font-medium text-green-700">{file.name}</p>
<p className="text-xs text-muted-foreground">
</p>
</div>
</div>
) : (
<>
<Upload className={cn(
"mb-2 h-10 w-10",
isDragOver ? "text-primary" : "text-muted-foreground"
)} />
<p className={cn(
"text-sm font-medium",
isDragOver ? "text-primary" : "text-muted-foreground"
)}>
{isDragOver ? "파일을 놓으세요" : "파일을 드래그하거나 클릭하여 선택"}
<Upload
className={cn(
"mb-2 h-8 w-8",
isDragOver ? "text-primary" : "text-muted-foreground"
)}
/>
<p
className={cn(
"text-sm font-medium",
isDragOver ? "text-primary" : "text-muted-foreground"
)}
>
{isDragOver
? "파일을 놓으세요"
: "파일을 드래그하거나 클릭하여 선택"}
</p>
<p className="mt-1 text-xs text-muted-foreground">
형식: .xlsx, .xls, .csv
@ -592,163 +596,148 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
</div>
</div>
{sheetNames.length > 0 && (
<div>
<Label htmlFor="sheet-select" className="text-xs sm:text-sm">
</Label>
<Select value={selectedSheet} onValueChange={handleSheetChange}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="시트를 선택하세요" />
</SelectTrigger>
<SelectContent>
{sheetNames.map((sheetName) => (
<SelectItem
key={sheetName}
value={sheetName}
className="text-xs sm:text-sm"
>
<FileSpreadsheet className="mr-2 inline h-4 w-4" />
{sheetName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
)}
{/* 파일이 선택된 경우에만 미리보기 표시 */}
{file && displayData.length > 0 && (
<>
{/* 시트 선택 + 행/열 편집 버튼 */}
<div className="flex flex-wrap items-center gap-3">
<div className="flex items-center gap-2">
<Label className="text-xs text-muted-foreground sm:text-sm">
:
</Label>
<Select value={selectedSheet} onValueChange={handleSheetChange}>
<SelectTrigger className="h-8 w-[140px] text-xs sm:h-9 sm:w-[180px] sm:text-sm">
<SelectValue placeholder="Sheet1" />
</SelectTrigger>
<SelectContent>
{sheetNames.map((sheetName) => (
<SelectItem
key={sheetName}
value={sheetName}
className="text-xs sm:text-sm"
>
{sheetName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 2단계: 범위 지정 */}
{currentStep === 2 && (
<div className="space-y-3">
{/* 상단: 시트 선택 + 버튼들 */}
<div className="flex flex-wrap items-center gap-3">
<div className="flex items-center gap-2">
<Label className="text-xs text-muted-foreground sm:text-sm">:</Label>
<Select value={selectedSheet} onValueChange={handleSheetChange}>
<SelectTrigger className="h-8 w-[140px] text-xs sm:h-10 sm:w-[180px] sm:text-sm">
<SelectValue placeholder="Sheet1" />
</SelectTrigger>
<SelectContent>
{sheetNames.map((sheetName) => (
<SelectItem key={sheetName} value={sheetName} className="text-xs sm:text-sm">
{sheetName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="ml-auto flex flex-wrap gap-1">
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddRow}
className="h-7 px-2 text-xs"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRemoveRow}
className="h-7 px-2 text-xs"
>
<Minus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddColumn}
className="h-7 px-2 text-xs"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRemoveColumn}
className="h-7 px-2 text-xs"
>
<Minus className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
<div className="ml-auto flex flex-wrap gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddRow}
className="h-7 text-xs sm:h-8 sm:text-sm"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAddColumn}
className="h-7 text-xs sm:h-8 sm:text-sm"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRemoveRow}
className="h-7 text-xs sm:h-8 sm:text-sm"
>
<Minus className="mr-1 h-3 w-3" />
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleRemoveColumn}
className="h-7 text-xs sm:h-8 sm:text-sm"
>
<Minus className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
{/* 감지된 범위 */}
<div className="text-xs text-muted-foreground">
: <span className="font-medium">{detectedRange}</span>
<span className="ml-2">({displayData.length} )</span>
</div>
{/* 하단: 감지된 범위 + 테이블 */}
<div className="text-xs text-muted-foreground sm:text-sm">
: <span className="font-medium">{detectedRange}</span>
<span className="ml-2 text-[10px] sm:text-xs">
,
</span>
</div>
{displayData.length > 0 && (
<div className="max-h-[250px] overflow-auto rounded-md border border-border">
<table className="min-w-full text-[10px] sm:text-xs">
<thead className="sticky top-0 bg-muted">
<tr>
<th className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium">
</th>
{excelColumns.map((col, index) => (
<th
key={col}
className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium"
>
{String.fromCharCode(65 + index)}
</th>
))}
</tr>
</thead>
<tbody>
<tr className="bg-primary/5">
<td className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium">
1
</td>
{excelColumns.map((col) => (
<td
key={col}
className="whitespace-nowrap border-b border-r border-border px-2 py-1 font-medium text-primary"
>
{col}
</td>
))}
</tr>
{displayData.map((row, rowIndex) => (
<tr key={rowIndex} className="border-b border-border last:border-0">
<td className="whitespace-nowrap border-r border-border bg-muted/50 px-2 py-1 text-center font-medium text-muted-foreground">
{rowIndex + 2}
{/* 데이터 미리보기 테이블 */}
<div className="max-h-[280px] overflow-auto rounded-md border border-border">
<table className="min-w-full text-[10px] sm:text-xs">
<thead className="sticky top-0 bg-muted">
<tr>
<th className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium"></th>
{excelColumns.map((col, index) => (
<th
key={col}
className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium"
>
{String.fromCharCode(65 + index)}
</th>
))}
</tr>
</thead>
<tbody>
<tr className="bg-primary/5">
<td className="whitespace-nowrap border-b border-r border-border bg-primary/10 px-2 py-1 text-center font-medium">
1
</td>
{excelColumns.map((col) => (
<td
key={col}
className="max-w-[150px] truncate whitespace-nowrap border-r border-border px-2 py-1"
title={String(row[col])}
className="whitespace-nowrap border-b border-r border-border px-2 py-1 font-medium text-primary"
>
{String(row[col] || "")}
{col}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{displayData.slice(0, 10).map((row, rowIndex) => (
<tr
key={rowIndex}
className="border-b border-border last:border-0"
>
<td className="whitespace-nowrap border-r border-border bg-muted/50 px-2 py-1 text-center font-medium text-muted-foreground">
{rowIndex + 2}
</td>
{excelColumns.map((col) => (
<td
key={col}
className="max-w-[150px] truncate whitespace-nowrap border-r border-border px-2 py-1"
title={String(row[col])}
>
{String(row[col] || "")}
</td>
))}
</tr>
))}
{displayData.length > 10 && (
<tr>
<td
colSpan={excelColumns.length + 1}
className="bg-muted/30 px-2 py-1 text-center text-muted-foreground"
>
... {displayData.length - 10}
</td>
</tr>
)}
</tbody>
</table>
</div>
</>
)}
</div>
)}
{/* 3단계: 컬럼 매핑 */}
{currentStep === 3 && (
{/* 2단계: 컬럼 매핑 */}
{currentStep === 2 && (
<div className="space-y-4">
{/* 상단: 제목 + 자동 매핑 버튼 */}
<div className="flex items-center justify-between">
@ -773,9 +762,12 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<div> </div>
</div>
<div className="max-h-[400px] space-y-2 overflow-y-auto">
<div className="max-h-[350px] space-y-2 overflow-y-auto">
{columnMappings.map((mapping, index) => (
<div key={index} className="grid grid-cols-[1fr_auto_1fr] items-center gap-2">
<div
key={index}
className="grid grid-cols-[1fr_auto_1fr] items-center gap-2"
>
<div className="rounded-md border border-border bg-muted px-3 py-2 text-xs font-medium sm:text-sm">
{mapping.excelColumn}
</div>
@ -793,7 +785,9 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<SelectValue placeholder="매핑 안함">
{mapping.systemColumn
? (() => {
const col = systemColumns.find(c => c.name === mapping.systemColumn);
const col = systemColumns.find(
(c) => c.name === mapping.systemColumn
);
return col?.label || mapping.systemColumn;
})()
: "매핑 안함"}
@ -821,27 +815,27 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
{/* 매핑 자동 저장 안내 */}
{isAutoMappingLoaded ? (
<div className="mt-4 rounded-md border border-success bg-success/10 p-3">
<div className="rounded-md border border-success bg-success/10 p-3">
<div className="flex items-start gap-2">
<CheckCircle2 className="mt-0.5 h-4 w-4 text-success" />
<div className="text-[10px] text-success sm:text-xs">
<p className="font-medium"> </p>
<p className="mt-1">
.
.
.
</p>
</div>
</div>
</div>
) : (
<div className="mt-4 rounded-md border border-muted bg-muted/30 p-3">
<div className="rounded-md border border-muted bg-muted/30 p-3">
<div className="flex items-start gap-2">
<Zap className="mt-0.5 h-4 w-4 text-muted-foreground" />
<div className="text-[10px] text-muted-foreground sm:text-xs">
<p className="font-medium"> </p>
<p className="mt-1">
.
.
.
.
</p>
</div>
</div>
@ -850,8 +844,8 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
</div>
)}
{/* 4단계: 확인 */}
{currentStep === 4 && (
{/* 3단계: 확인 */}
{currentStep === 3 && (
<div className="space-y-4">
<div className="rounded-md border border-border bg-muted/50 p-4">
<h3 className="text-sm font-medium sm:text-base"> </h3>
@ -871,7 +865,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<p>
<span className="font-medium">:</span>{" "}
{uploadMode === "insert"
? "삽입"
? "신규 등록"
: uploadMode === "update"
? "업데이트"
: "Upsert"}
@ -884,12 +878,17 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<div className="mt-2 space-y-1 text-[10px] text-muted-foreground sm:text-xs">
{columnMappings
.filter((m) => m.systemColumn)
.map((mapping, index) => (
<p key={index}>
<span className="font-medium">{mapping.excelColumn}</span> {" "}
{mapping.systemColumn}
</p>
))}
.map((mapping, index) => {
const col = systemColumns.find(
(c) => c.name === mapping.systemColumn
);
return (
<p key={index}>
<span className="font-medium">{mapping.excelColumn}</span> {" "}
{col?.label || mapping.systemColumn}
</p>
);
})}
{columnMappings.filter((m) => m.systemColumn).length === 0 && (
<p className="text-destructive"> .</p>
)}
@ -902,7 +901,8 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<div className="text-[10px] text-warning sm:text-xs">
<p className="font-medium"></p>
<p className="mt-1">
. ?
.
?
</p>
</div>
</div>
@ -920,10 +920,10 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
>
{currentStep === 1 ? "취소" : "이전"}
</Button>
{currentStep < 4 ? (
{currentStep < 3 ? (
<Button
onClick={handleNext}
disabled={isUploading}
disabled={isUploading || (currentStep === 1 && !file)}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
@ -931,10 +931,12 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
) : (
<Button
onClick={handleUpload}
disabled={isUploading || columnMappings.filter((m) => m.systemColumn).length === 0}
disabled={
isUploading || columnMappings.filter((m) => m.systemColumn).length === 0
}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
{isUploading ? "업로드 중..." : "다음"}
{isUploading ? "업로드 중..." : "업로드"}
</Button>
)}
</DialogFooter>