"use client"; import { useState, useTransition, useRef } from "react"; import { Upload, FileText, CheckCircle, AlertCircle, Loader } from "lucide-react"; import { parseCSV, applyImport, type PreviewResult, type ImportOptions, type ApplyResult, } from "@/app/admin/import-export/import-actions"; type Course = { id: string; title: string }; const inputStyle: React.CSSProperties = { border: "2px solid var(--border)", background: "var(--background)", outline: "none", width: "100%", padding: "0.5rem 0.75rem", fontSize: "16px", fontFamily: "inherit", }; const focusHandlers = { onFocus: (e: React.FocusEvent) => (e.currentTarget.style.borderColor = "var(--foreground)"), onBlur: (e: React.FocusEvent) => (e.currentTarget.style.borderColor = "var(--border)"), }; function Toggle({ label, hint, checked, onChange }: { label: string; hint?: string; checked: boolean; onChange: (v: boolean) => void }) { return (

{label}

{hint &&

{hint}

}
); } function StepIndicator({ step }: { step: number }) { const steps = ["Загрузка", "Предпросмотр", "Опции", "Готово"]; return (
{steps.map((label, i) => { const num = i + 1; const active = num === step; const done = num < step; return (
{done ? "✓" : num}
{label}
{i < steps.length - 1 && (
)}
); })}
); } // ── Main component ──────────────────────────────────────────────────────────── export function CsvImporter({ courses }: { courses: Course[] }) { const [step, setStep] = useState(1); const [pending, startTransition] = useTransition(); const [error, setError] = useState(null); const fileRef = useRef(null); // Step 1 state const [encoding, setEncoding] = useState<"utf8" | "win1251">("utf8"); const [updateExisting, setUpdateExisting] = useState(false); const [fileBase64, setFileBase64] = useState(null); const [fileName, setFileName] = useState(null); // Step 2 state const [preview, setPreview] = useState(null); // Step 3 state const [autoVerifyEmail, setAutoVerifyEmail] = useState(true); const [courseId, setCourseId] = useState(""); const [accessDays, setAccessDays] = useState("0"); const [sendWelcome, setSendWelcome] = useState(false); // Step 4 state const [result, setResult] = useState(null); function handleFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setFileName(file.name); const reader = new FileReader(); reader.onload = () => { const ab = reader.result as ArrayBuffer; const bytes = new Uint8Array(ab); let binary = ""; bytes.forEach((b) => (binary += String.fromCharCode(b))); setFileBase64(btoa(binary)); }; reader.readAsArrayBuffer(file); } function handleParse() { if (!fileBase64) return; setError(null); startTransition(async () => { try { const result = await parseCSV(fileBase64, encoding, updateExisting); setPreview(result); setStep(2); } catch (e) { setError(e instanceof Error ? e.message : "Ошибка разбора файла"); } }); } function handleApply() { if (!preview) return; setError(null); const options: ImportOptions = { updateExisting, autoVerifyEmail, courseId: courseId || undefined, accessDays: parseInt(accessDays) || 0, sendWelcome, encoding, }; startTransition(async () => { try { const r = await applyImport(preview.rows, options); setResult(r); setStep(4); } catch (e) { setError(e instanceof Error ? e.message : "Ошибка импорта"); } }); } function handleReset() { setStep(1); setFileBase64(null); setFileName(null); setPreview(null); setResult(null); setError(null); if (fileRef.current) fileRef.current.value = ""; } return (
{/* ── Step 1: Upload ── */} {step === 1 && (
{/* File picker */}
fileRef.current?.click()} > {fileName ? (

{fileName}

Нажмите чтобы выбрать другой файл

) : (

Выберите CSV-файл

Поддерживаются файлы из emdesell, Excel и любого табличного редактора

)}
{/* Options */}
{/* Template download hint */}

Ожидаемые колонки: Email, Имя, Фамилия, Телефон (порядок не важен, первая строка — заголовки).

{error &&

{error}

}
)} {/* ── Step 2: Preview ── */} {step === 2 && preview && (
{/* Stats */}
{[ { label: "Будет создано", count: preview.countNew, color: "#3A6A3A" }, { label: "Будет обновлено", count: preview.countUpdate, color: "var(--foreground)" }, { label: "Ошибок", count: preview.countError, color: "oklch(0.577 0.245 27.325)" }, ].map(({ label, count, color }) => (

{count}

{label}

))}
{/* Table */}
{["#", "Email", "Имя", "Телефон", "Статус"].map((h) => ( ))} {preview.rows.map((row) => ( ))}
{h}
{row.index} {row.email} {row.name} {row.phone || "—"} {row.status === "new" && ✦ Новый} {row.status === "update" && ↻ Обновить} {row.status === "error" && ( {row.errorMsg} )}
)} {/* ── Step 3: Options ── */} {step === 3 && (
{courseId && (
setAccessDays(e.target.value)} placeholder="0 — бессрочно" style={inputStyle} {...focusHandlers} />

0 = бессрочный доступ

)} {error &&

{error}

}
)} {/* ── Step 4: Result ── */} {step === 4 && result && (

Импорт завершён

Создано: {result.created} · Обновлено: {result.updated}

{result.errors.length > 0 && (

Ошибки ({result.errors.length})

{result.errors.map((e, i) => (

{e}

))}
)}
)}
); }