- Remove all gradient backgrounds for clean white interface - Update color scheme to neutral shadcn.com palette - Simplify card headers (remove icons and colored backgrounds) - Clean up spacing and reduce visual noise - Update buttons to use outline variants - Simplify table and stats styling with muted colors - Improve typography with better font smoothing - Match shadcn.com's minimal, professional aesthetic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
234 lines
8.6 KiB
TypeScript
234 lines
8.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import API_URL from '@/lib/api'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Upload, BarChart3, Loader2, FileText, CheckCircle2, AlertCircle } from 'lucide-react'
|
|
|
|
interface DataPreviewProps {
|
|
data: any
|
|
loading: boolean
|
|
formData: any
|
|
}
|
|
|
|
export default function DataPreview({ data, loading, formData }: DataPreviewProps) {
|
|
const [filling, setFilling] = useState(false)
|
|
const [fillResult, setFillResult] = useState<any>(null)
|
|
|
|
const handleFillToWebsite = async () => {
|
|
if (!formData) return
|
|
|
|
setFilling(true)
|
|
setFillResult(null)
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}/api/fill/journeybook`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
username: formData.username,
|
|
password: formData.password,
|
|
start_date: formData.startDate,
|
|
end_date: formData.endDate,
|
|
start_km: parseInt(formData.startKm),
|
|
end_km: parseInt(formData.endKm),
|
|
vehicle_registration: formData.vehicleRegistration,
|
|
variance: parseFloat(formData.variance),
|
|
dry_run: false
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json()
|
|
throw new Error(errorData.detail || 'Chyba při vyplňování dat')
|
|
}
|
|
|
|
const result = await response.json()
|
|
setFillResult(result)
|
|
} catch (err: any) {
|
|
alert('Chyba: ' + err.message)
|
|
} finally {
|
|
setFilling(false)
|
|
}
|
|
}
|
|
if (loading) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="flex flex-col items-center justify-center h-96">
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
<p className="mt-4 text-sm text-muted-foreground">Načítání dat...</p>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
if (!data) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Náhled dat</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
<FileText className="h-12 w-12 text-muted-foreground mb-4" />
|
|
<p className="text-sm text-muted-foreground">
|
|
Vyplňte formulář a klikněte na "Vypočítat"
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Náhled dat</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
|
|
<div className="mb-4 grid grid-cols-2 md:grid-cols-3 gap-4 p-4 rounded-lg border bg-muted/50">
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Měsíc</p>
|
|
<p className="font-medium">{data.month}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Celkem záznamů</p>
|
|
<p className="font-medium">{data.total_entries}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Počáteční km</p>
|
|
<p className="font-medium">{data.start_km.toLocaleString()}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Koncový km</p>
|
|
<p className="font-medium">{data.end_km.toLocaleString()}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Celkem ujeto</p>
|
|
<p className="font-medium">{(data.end_km - data.start_km).toLocaleString()} km</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-xs text-muted-foreground">Filtrováno dnů</p>
|
|
<p className="font-medium">{data.filtered_days}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-auto max-h-96 rounded-md border">
|
|
<table className="w-full text-sm">
|
|
<thead className="bg-muted/50 sticky top-0">
|
|
<tr>
|
|
{data.entries.length > 0 && Object.keys(data.entries[0]).map((key: string) => (
|
|
<th key={key} className="px-4 py-2 text-left font-medium border-b whitespace-nowrap">
|
|
{key}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y">
|
|
{data.entries.map((entry: any, index: number) => (
|
|
<tr key={index} className="hover:bg-muted/50">
|
|
{Object.keys(entry).map((key: string) => (
|
|
<td key={key} className="px-4 py-2 text-right">
|
|
{entry[key] !== null && entry[key] !== undefined ? entry[key] : '-'}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{formData && formData.startDate === '2025-01-01' && (
|
|
<div className="mt-4">
|
|
<Button
|
|
onClick={handleFillToWebsite}
|
|
disabled={filling}
|
|
variant="secondary"
|
|
className="w-full"
|
|
>
|
|
{filling ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Vyplňování...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Upload className="mr-2 h-4 w-4" />
|
|
Vyplnit na web
|
|
</>
|
|
)}
|
|
</Button>
|
|
|
|
{fillResult && (
|
|
<div className={`mt-4 rounded-md border p-4 ${fillResult.dry_run ? 'bg-blue-50 border-blue-200' : 'bg-green-50 border-green-200'}`}>
|
|
<h3 className={`text-sm font-semibold mb-2 flex items-center gap-2 ${fillResult.dry_run ? 'text-blue-900' : 'text-green-900'}`}>
|
|
{fillResult.dry_run ? (
|
|
<>
|
|
<AlertCircle className="h-5 w-5" />
|
|
Výsledek DRY RUN:
|
|
</>
|
|
) : (
|
|
<>
|
|
<CheckCircle2 className="h-5 w-5" />
|
|
Výsledek vyplňování:
|
|
</>
|
|
)}
|
|
</h3>
|
|
<div className={`text-sm ${fillResult.dry_run ? 'text-blue-800' : 'text-green-800'}`}>
|
|
<p>Měsíc: {fillResult.month}</p>
|
|
{fillResult.dry_run ? (
|
|
<>
|
|
<p>Připraveno aktualizací: {fillResult.updates_prepared}</p>
|
|
<p>Připraveno smazání: {fillResult.deletes_prepared}</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
<p>Celkem aktualizací: {fillResult.updates_total}</p>
|
|
<p>Úspěšně vyplněno: {fillResult.updates_successful}</p>
|
|
<p>Chyby při vyplňování: {fillResult.updates_failed}</p>
|
|
{fillResult.deletes_total > 0 && (
|
|
<>
|
|
<p className="mt-2">Celkem smazání: {fillResult.deletes_total}</p>
|
|
<p>Úspěšně smazáno: {fillResult.deletes_successful}</p>
|
|
<p>Chyby při mazání: {fillResult.deletes_failed}</p>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
{fillResult.dry_run && (
|
|
<p className="mt-2 font-semibold text-orange-700 flex items-center gap-2">
|
|
<AlertCircle className="h-4 w-4" />
|
|
DRY RUN MODE - Data nebyla odeslána na web
|
|
</p>
|
|
)}
|
|
{!fillResult.dry_run && fillResult.updates_successful > 0 && (
|
|
<p className="mt-2 font-semibold text-green-700 flex items-center gap-2">
|
|
<CheckCircle2 className="h-4 w-4" />
|
|
Data byla úspěšně vyplněna. Nyní zkontrolujte na webu a klikněte "Uzavřít měsíc" ručně.
|
|
</p>
|
|
)}
|
|
{!fillResult.dry_run && fillResult.errors && fillResult.errors.length > 0 && (
|
|
<div className="mt-2 p-2 bg-red-100 border border-red-300 rounded">
|
|
<p className="font-semibold text-red-900 flex items-center gap-2">
|
|
<AlertCircle className="h-4 w-4" />
|
|
Chyby:
|
|
</p>
|
|
{fillResult.errors.map((err: any, idx: number) => (
|
|
<p key={idx} className="text-xs text-red-800">• {err.type} řádek {err.row}: {err.error}</p>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|