Files
infohliadka/pages/admin/bulk-import.tsx
Lukas Davidovic 249a672cd7 transform admin panel with comprehensive professional UI
- migrate from SQLite to PostgreSQL with Drizzle ORM
- implement comprehensive AdminLayout with expandable sidebar navigation
- create professional dashboard with real-time charts and metrics
- add advanced monitoring, reporting, and export functionality
- fix menu alignment and remove non-existent pages
- eliminate duplicate headers and improve UI consistency
- add Tailwind CSS v3 for professional styling
- expand database schema from 6 to 15 tables
- implement role-based access control and API key management
- create comprehensive settings, monitoring, and system info pages
2025-09-06 15:14:20 +02:00

205 lines
8.3 KiB
TypeScript

import { useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import AdminLayout from '../../components/AdminLayout'
import { CloudArrowUpIcon, DocumentArrowUpIcon, CheckCircleIcon } from '@heroicons/react/24/outline'
const BulkImport: NextPage = () => {
const [file, setFile] = useState<File | null>(null)
const [importing, setImporting] = useState(false)
const [results, setResults] = useState<any>(null)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0])
setResults(null)
}
}
const handleImport = async () => {
if (!file) return
setImporting(true)
const formData = new FormData()
formData.append('file', file)
try {
const response = await fetch('/api/admin/bulk-import', {
method: 'POST',
body: formData
})
if (response.ok) {
const data = await response.json()
setResults(data)
setFile(null)
} else {
const error = await response.json()
alert('Chyba pri importe: ' + error.error)
}
} catch (error) {
alert('Chyba pri nahrávaní súboru')
} finally {
setImporting(false)
}
}
return (
<>
<Head>
<title>Hromadný import - Hliadka.sk Admin</title>
</Head>
<AdminLayout title="Hromadný import">
<div className="px-4 py-6 sm:px-6 lg:px-8">
<div className="sm:flex sm:items-center">
<div className="sm:flex-auto">
<p className="text-sm text-gray-700">
Importujte viacero zdrojov naraz pomocou CSV súboru
</p>
</div>
</div>
<div className="mt-8 max-w-3xl">
<div className="card p-6">
<div className="text-center">
<CloudArrowUpIcon className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-semibold text-gray-900">Nahranie súboru</h3>
<p className="mt-1 text-sm text-gray-500">
Vyberte CSV súbor s URL adresami zdrojov na import
</p>
</div>
<div className="mt-6">
<div className="flex justify-center px-6 py-10 border-2 border-gray-300 border-dashed rounded-md">
<div className="text-center">
<DocumentArrowUpIcon className="mx-auto h-12 w-12 text-gray-400" />
<div className="mt-4">
<label htmlFor="file-upload" className="cursor-pointer">
<span className="mt-2 block text-sm font-semibold text-gray-900">
Nahrať súbor
</span>
<input
id="file-upload"
name="file-upload"
type="file"
accept=".csv,.txt"
className="sr-only"
onChange={handleFileChange}
/>
</label>
<p className="text-sm text-gray-500">
CSV alebo TXT súbor do 10MB
</p>
</div>
</div>
</div>
{file && (
<div className="mt-4">
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-md">
<div className="flex items-center">
<DocumentArrowUpIcon className="h-5 w-5 text-gray-400 mr-2" />
<span className="text-sm font-medium text-gray-900">{file.name}</span>
<span className="ml-2 text-sm text-gray-500">
({Math.round(file.size / 1024)} KB)
</span>
</div>
<button
onClick={() => setFile(null)}
className="text-sm text-red-600 hover:text-red-800"
>
Odstrániť
</button>
</div>
</div>
)}
{file && (
<div className="mt-6 flex justify-end">
<button
onClick={handleImport}
disabled={importing}
className="btn-primary"
>
{importing ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Importuje sa...
</>
) : (
<>
<CloudArrowUpIcon className="h-4 w-4 mr-2" />
Spustiť import
</>
)}
</button>
</div>
)}
</div>
</div>
{results && (
<div className="mt-8">
<div className="card p-6">
<div className="flex items-center mb-4">
<CheckCircleIcon className="h-6 w-6 text-green-500 mr-3" />
<h3 className="text-lg font-medium text-gray-900">Import dokončený</h3>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div className="bg-green-50 p-4 rounded-lg">
<div className="text-2xl font-bold text-green-600">{results.imported || 0}</div>
<div className="text-sm text-green-700">Úspešne importované</div>
</div>
<div className="bg-yellow-50 p-4 rounded-lg">
<div className="text-2xl font-bold text-yellow-600">{results.skipped || 0}</div>
<div className="text-sm text-yellow-700">Preskočené (duplikáty)</div>
</div>
<div className="bg-red-50 p-4 rounded-lg">
<div className="text-2xl font-bold text-red-600">{results.failed || 0}</div>
<div className="text-sm text-red-700">Neúspešné</div>
</div>
</div>
{results.errors && results.errors.length > 0 && (
<div className="mt-6">
<h4 className="text-sm font-medium text-gray-900 mb-2">Chyby pri importe:</h4>
<div className="bg-red-50 rounded-md p-3">
<ul className="text-sm text-red-700 space-y-1">
{results.errors.map((error: string, idx: number) => (
<li key={idx}> {error}</li>
))}
</ul>
</div>
</div>
)}
</div>
</div>
)}
<div className="mt-8">
<div className="card p-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">Inštrukcie</h3>
<div className="prose prose-sm text-gray-500">
<ul>
<li>CSV súbor by mal obsahovať jeden URL na riadok</li>
<li>Prvý riadok môže obsahovať hlavičku (bude preskočený)</li>
<li>Podporované formáty: .csv, .txt</li>
<li>Maximálna veľkosť súboru: 10MB</li>
<li>Duplikátne URL adresy budú automaticky preskočené</li>
<li>Neplatné URL adresy budú označené ako chyby</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</AdminLayout>
</>
)
}
export default BulkImport