459 lines
15 KiB
TypeScript
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>
|
|
)
|
|
}
|
|
|