Files
fabrikanabytok/apps/fabrikanabytok/components/admin/employee-migration-panel.tsx
2025-11-28 20:47:59 +01:00

459 lines
15 KiB
TypeScript

"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Badge } from "@/components/ui/badge"
import {
Database,
Loader2,
CheckCircle2,
AlertCircle,
Info,
Play,
RotateCcw,
Shield,
} from "lucide-react"
import {
runEmployeeMigration,
verifyMigration,
rollbackMigration,
getMigrationPreview,
} from "@/lib/actions/employee-migration.actions"
interface MigrationResult {
success: boolean
message: string
data?: {
migrated?: number
total?: number
errors?: string[]
totalEmployees?: number
missingUserId?: number
invalidUserIdReferences?: number
totalToMigrate?: number
willCreateNewUsers?: number
willLinkExisting?: number
willUpdateRoles?: number
preview?: Array<{
email: string
firstName: string
lastName: string
role: string
hasExistingUser: boolean
existingUserRole?: string
willCreateUser: boolean
willUpdateRole: boolean
}>
}
}
export function EmployeeMigrationPanel() {
const [loading, setLoading] = useState(false)
const [verifying, setVerifying] = useState(false)
const [loadingPreview, setLoadingPreview] = useState(false)
const [result, setResult] = useState<MigrationResult | null>(null)
const [verificationResult, setVerificationResult] = useState<MigrationResult | null>(null)
const [preview, setPreview] = useState<MigrationResult | null>(null)
const handlePreview = async () => {
setLoadingPreview(true)
setPreview(null)
try {
const previewResult = await getMigrationPreview()
setPreview(previewResult)
} catch (error) {
setPreview({
success: false,
message: "Hiba történt az előnézet betöltése során",
})
} finally {
setLoadingPreview(false)
}
}
const handleMigration = async () => {
setLoading(true)
setResult(null)
try {
const migrationResult = await runEmployeeMigration()
setResult(migrationResult)
// Auto-verify after successful migration
if (migrationResult.success) {
setTimeout(() => {
handleVerification()
}, 1000)
}
} catch (error) {
setResult({
success: false,
message: "Váratlan hiba történt a migráció során",
})
} finally {
setLoading(false)
}
}
const handleVerification = async () => {
setVerifying(true)
setVerificationResult(null)
try {
const verifyResult = await verifyMigration()
setVerificationResult(verifyResult)
} catch (error) {
setVerificationResult({
success: false,
message: "Hiba történt az ellenőrzés során",
})
} finally {
setVerifying(false)
}
}
const handleRollback = async () => {
setLoading(true)
try {
const rollbackResult = await rollbackMigration()
setResult(rollbackResult)
setVerificationResult(null)
} catch (error) {
setResult({
success: false,
message: "Hiba történt a visszaállítás során",
})
} finally {
setLoading(false)
}
}
return (
<Card className="border-orange-200 bg-orange-50/50">
<CardHeader>
<div className="flex items-center gap-2">
<Database className="w-5 h-5 text-orange-600" />
<CardTitle>Munkatársak migrálása</CardTitle>
<Badge variant="destructive" className="ml-auto">
<Shield className="w-3 h-3 mr-1" />
Superadmin
</Badge>
</div>
<CardDescription>
Migrálja a meglévő munkatársakat az új egységes hitelesítési rendszerbe
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Info Alert */}
<Alert>
<Info className="h-4 w-4" />
<AlertTitle>Migráció információk</AlertTitle>
<AlertDescription className="space-y-2 text-sm">
<p>Ez a folyamat:</p>
<ul className="list-disc list-inside space-y-1 ml-2">
<li>
Megkeresi az összes munkatársat, akiknek még nincs{" "}
<code className="text-xs bg-muted px-1 py-0.5 rounded">userId</code> mezőjük
</li>
<li>Létrehoz nekik felhasználói fiókot a users táblában</li>
<li>Összeköti a munkatársi rekordokat a felhasználói fiókokkal</li>
<li>
<strong>MEGŐRZI</strong> a meglévő regisztrált felhasználókat (nem írja felül)
</li>
<li>Eltávolítja a duplikált jelszó mezőket a munkatársi táblából</li>
</ul>
</AlertDescription>
</Alert>
{/* Migration Result */}
{result && (
<Alert variant={result.success ? "default" : "destructive"}>
{result.success ? (
<CheckCircle2 className="h-4 w-4" />
) : (
<AlertCircle className="h-4 w-4" />
)}
<AlertTitle>
{result.success ? "Sikeres migráció" : "Migráció sikertelen"}
</AlertTitle>
<AlertDescription className="space-y-2">
<p>{result.message}</p>
{result.data && (
<div className="text-sm space-y-1 mt-2">
{result.data.migrated !== undefined && (
<p>
Migrált munkatársak:{" "}
<strong>
{result.data.migrated} / {result.data.total || 0}
</strong>
</p>
)}
{result.data.errors && result.data.errors.length > 0 && (
<div className="mt-2">
<p className="font-semibold">Hibák:</p>
<ul className="list-disc list-inside ml-2 max-h-32 overflow-y-auto">
{result.data.errors.map((error, idx) => (
<li key={idx} className="text-xs">
{error}
</li>
))}
</ul>
</div>
)}
</div>
)}
</AlertDescription>
</Alert>
)}
{/* Preview Result */}
{preview && (
<Alert variant={preview.success ? "default" : "destructive"}>
{preview.success ? (
<Info className="h-4 w-4" />
) : (
<AlertCircle className="h-4 w-4" />
)}
<AlertTitle>Migráció előnézet</AlertTitle>
<AlertDescription className="space-y-2">
<p>{preview.message}</p>
{preview.data && (
<div className="text-sm space-y-2 mt-2">
<div className="grid grid-cols-2 gap-2">
<div>
<p className="text-muted-foreground">Migrálásra vár:</p>
<p className="font-bold text-lg">{preview.data.totalToMigrate}</p>
</div>
<div>
<p className="text-muted-foreground">Új felhasználók:</p>
<p className="font-bold text-lg text-blue-600">
{preview.data.willCreateNewUsers}
</p>
</div>
<div>
<p className="text-muted-foreground">Meglévőkhöz kapcsolódik:</p>
<p className="font-bold text-lg text-green-600">
{preview.data.willLinkExisting}
</p>
</div>
<div>
<p className="text-muted-foreground">Szerepkör frissítés:</p>
<p className="font-bold text-lg text-orange-600">
{preview.data.willUpdateRoles}
</p>
</div>
</div>
{preview.data.preview && preview.data.preview.length > 0 && (
<div className="mt-3">
<p className="font-semibold mb-1">
Első {preview.data.preview.length} munkatárs:
</p>
<div className="max-h-40 overflow-y-auto bg-muted/30 rounded p-2 space-y-1">
{preview.data.preview.map((item, idx) => (
<div key={idx} className="text-xs flex items-center gap-2">
<span className="font-medium">
{item.firstName} {item.lastName}
</span>
<span className="text-muted-foreground">({item.email})</span>
{item.willCreateUser ? (
<Badge variant="default" className="text-xs">Új</Badge>
) : (
<Badge variant="secondary" className="text-xs">Meglévő</Badge>
)}
{item.willUpdateRole && (
<Badge variant="outline" className="text-xs">
{item.existingUserRole} {item.role}
</Badge>
)}
</div>
))}
</div>
</div>
)}
</div>
)}
</AlertDescription>
</Alert>
)}
{/* Verification Result */}
{verificationResult && (
<Alert variant={verificationResult.success ? "default" : "destructive"}>
{verificationResult.success ? (
<CheckCircle2 className="h-4 w-4" />
) : (
<AlertCircle className="h-4 w-4" />
)}
<AlertTitle>Ellenőrzés eredménye</AlertTitle>
<AlertDescription className="space-y-2">
<p>{verificationResult.message}</p>
{verificationResult.data && (
<div className="text-sm space-y-1 mt-2">
<p>
Összes munkatárs: <strong>{verificationResult.data.totalEmployees}</strong>
</p>
<p>
Hiányzó userId:{" "}
<strong
className={
verificationResult.data.missingUserId === 0
? "text-green-600"
: "text-red-600"
}
>
{verificationResult.data.missingUserId}
</strong>
</p>
<p>
Érvénytelen userId referenciák:{" "}
<strong
className={
verificationResult.data.invalidUserIdReferences === 0
? "text-green-600"
: "text-red-600"
}
>
{verificationResult.data.invalidUserIdReferences}
</strong>
</p>
</div>
)}
</AlertDescription>
</Alert>
)}
</CardContent>
<CardFooter className="flex gap-2 flex-wrap">
{/* Preview Button */}
<Button
onClick={handlePreview}
disabled={loading || verifying || loadingPreview}
variant="outline"
>
{loadingPreview ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Betöltés...
</>
) : (
<>
<Info className="w-4 h-4 mr-2" />
Előnézet
</>
)}
</Button>
{/* Run Migration Button */}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button disabled={loading || verifying || loadingPreview} variant="default">
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Migráció folyamatban...
</>
) : (
<>
<Play className="w-4 h-4 mr-2" />
Migráció futtatása
</>
)}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Biztos folytatni szeretné?</AlertDialogTitle>
<AlertDialogDescription>
Ez a művelet migrálja az összes munkatársat az új rendszerbe. A meglévő
felhasználók nem lesznek érintve. Az adatbázis módosul, de a folyamat
visszaállítható.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Mégse</AlertDialogCancel>
<AlertDialogAction onClick={handleMigration}>
Migráció indítása
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Verify Button */}
<Button
onClick={handleVerification}
disabled={loading || verifying || loadingPreview}
variant="secondary"
>
{verifying ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Ellenőrzés...
</>
) : (
<>
<CheckCircle2 className="w-4 h-4 mr-2" />
Ellenőrzés
</>
)}
</Button>
{/* Rollback Button (only show if migration was run) */}
{result && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
disabled={loading || verifying || loadingPreview}
variant="destructive"
className="ml-auto"
>
<RotateCcw className="w-4 h-4 mr-2" />
Visszaállítás
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Migráció visszaállítása?</AlertDialogTitle>
<AlertDialogDescription>
<strong className="text-destructive">FIGYELEM:</strong> Ez eltávolítja a userId
mezőt az összes munkatársi rekordból. Csak tesztelési célokra használja!
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Mégse</AlertDialogCancel>
<AlertDialogAction onClick={handleRollback} className="bg-destructive">
Visszaállítás
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</CardFooter>
</Card>
)
}