882 lines
28 KiB
TypeScript
882 lines
28 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Container,
|
|
Paper,
|
|
Typography,
|
|
Box,
|
|
Button,
|
|
Grid,
|
|
Card,
|
|
CardContent,
|
|
Chip,
|
|
Divider,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
TextField,
|
|
MenuItem,
|
|
IconButton,
|
|
Tooltip,
|
|
Alert,
|
|
} from "@mui/material";
|
|
import {
|
|
Download,
|
|
Delete,
|
|
Refresh,
|
|
ArrowBack,
|
|
Edit,
|
|
Save,
|
|
VpnKey,
|
|
ContentCopy,
|
|
SwapVert,
|
|
} from "@mui/icons-material";
|
|
import { useParams, useNavigate } from "react-router-dom";
|
|
import dayjs from "dayjs";
|
|
|
|
// Store
|
|
import { useApplicationStore } from "../store/applicationStore";
|
|
|
|
// Types
|
|
import { VSM_FINANCING_LABELS, QSM_FINANCING_LABELS } from "../types/api";
|
|
|
|
// Components
|
|
import LoadingSpinner from "../components/LoadingSpinner/LoadingSpinner";
|
|
|
|
// Utils
|
|
import { translateStatus, getStatusColor } from "../utils/statusTranslations";
|
|
|
|
const AdminApplicationView: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const { paId } = useParams<{ paId: string }>();
|
|
|
|
// Store
|
|
const {
|
|
currentApplication,
|
|
formData,
|
|
isLoading,
|
|
error,
|
|
successMessage,
|
|
loadApplicationAdmin,
|
|
updateApplicationStatusAdmin,
|
|
deleteApplicationAdmin,
|
|
downloadApplicationPdfAdmin,
|
|
resetApplicationCredentials,
|
|
isAdmin,
|
|
} = useApplicationStore();
|
|
|
|
// Local state
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
const [editingStatus, setEditingStatus] = useState(false);
|
|
const [newStatus, setNewStatus] = useState("");
|
|
const [showResetDialog, setShowResetDialog] = useState(false);
|
|
const [newCredentials, setNewCredentials] = useState<{
|
|
pa_id: string;
|
|
pa_key: string;
|
|
} | null>(null);
|
|
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
|
|
// Redirect if not admin
|
|
useEffect(() => {
|
|
if (!isAdmin) {
|
|
navigate("/");
|
|
return;
|
|
}
|
|
}, [isAdmin, navigate]);
|
|
|
|
// Load application on mount
|
|
useEffect(() => {
|
|
if (paId && isAdmin) {
|
|
loadApplicationAdmin(paId);
|
|
}
|
|
}, [paId, isAdmin, loadApplicationAdmin]);
|
|
|
|
// Update status when application loads
|
|
useEffect(() => {
|
|
if (currentApplication) {
|
|
setNewStatus(currentApplication.status);
|
|
}
|
|
}, [currentApplication]);
|
|
|
|
// Handle status change
|
|
const handleStatusChange = async () => {
|
|
if (paId && newStatus !== currentApplication?.status) {
|
|
const success = await updateApplicationStatusAdmin(paId, newStatus);
|
|
if (success) {
|
|
setEditingStatus(false);
|
|
}
|
|
} else {
|
|
setEditingStatus(false);
|
|
}
|
|
};
|
|
|
|
// Handle delete
|
|
const handleDelete = async () => {
|
|
if (paId) {
|
|
const success = await deleteApplicationAdmin(paId);
|
|
if (success) {
|
|
navigate("/admin");
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle refresh
|
|
const handleRefresh = () => {
|
|
if (paId) {
|
|
loadApplicationAdmin(paId);
|
|
}
|
|
};
|
|
|
|
// Handle reset credentials
|
|
const handleResetCredentials = async () => {
|
|
if (paId) {
|
|
const result = await resetApplicationCredentials(paId);
|
|
if (result) {
|
|
setNewCredentials(result);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Copy to clipboard function
|
|
const copyToClipboard = (text: string, field: string) => {
|
|
navigator.clipboard.writeText(text);
|
|
setCopiedField(field);
|
|
setTimeout(() => setCopiedField(null), 2000);
|
|
};
|
|
|
|
// Handle download PDF
|
|
const handleDownloadPdf = async () => {
|
|
if (paId) {
|
|
await downloadApplicationPdfAdmin(paId);
|
|
}
|
|
};
|
|
|
|
if (!isAdmin) {
|
|
return null; // Will redirect
|
|
}
|
|
|
|
if (!paId) {
|
|
return (
|
|
<Container maxWidth="md" sx={{ mt: 4 }}>
|
|
<Alert severity="error">Keine Antrags-ID angegeben.</Alert>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
if (isLoading) {
|
|
return <LoadingSpinner overlay text="Antrag wird geladen..." />;
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Container maxWidth="md" sx={{ mt: 4 }}>
|
|
<Alert
|
|
severity="error"
|
|
action={
|
|
<Button size="small" onClick={handleRefresh}>
|
|
<Refresh />
|
|
</Button>
|
|
}
|
|
>
|
|
{error}
|
|
</Alert>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
if (!currentApplication) {
|
|
return (
|
|
<Container maxWidth="md" sx={{ mt: 4 }}>
|
|
<Alert severity="warning">Antrag nicht gefunden.</Alert>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
|
{/* Header */}
|
|
<Box sx={{ mb: 3 }}>
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
alignItems: "flex-start",
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<Box>
|
|
<Button
|
|
startIcon={<ArrowBack />}
|
|
onClick={() => navigate("/admin")}
|
|
sx={{ mb: 2 }}
|
|
>
|
|
Zurück zum Dashboard
|
|
</Button>
|
|
<Typography variant="h3" component="h1" gutterBottom>
|
|
Antrag {currentApplication.pa_id}
|
|
</Typography>
|
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
|
{editingStatus ? (
|
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
|
<TextField
|
|
select
|
|
size="small"
|
|
value={newStatus}
|
|
onChange={(e) => setNewStatus(e.target.value)}
|
|
sx={{ minWidth: 100 }}
|
|
>
|
|
<MenuItem value="new">Neu</MenuItem>
|
|
<MenuItem value="in-review">In Prüfung</MenuItem>
|
|
<MenuItem value="approved">Genehmigt</MenuItem>
|
|
<MenuItem value="rejected">Abgelehnt</MenuItem>
|
|
</TextField>
|
|
<IconButton size="small" onClick={handleStatusChange}>
|
|
<Save />
|
|
</IconButton>
|
|
</Box>
|
|
) : (
|
|
<Chip
|
|
label={translateStatus(currentApplication.status)}
|
|
color={getStatusColor(currentApplication.status)}
|
|
onClick={() => setEditingStatus(true)}
|
|
sx={{ cursor: "pointer" }}
|
|
/>
|
|
)}
|
|
<Chip
|
|
label={currentApplication.variant}
|
|
variant="outlined"
|
|
size="small"
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Action Buttons */}
|
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
|
<Tooltip title="Aktualisieren">
|
|
<IconButton onClick={handleRefresh}>
|
|
<Refresh />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Bearbeiten">
|
|
<IconButton
|
|
onClick={() => navigate(`/admin/applications/${paId}/edit`)}
|
|
>
|
|
<Edit />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Zugangsdaten zurücksetzen">
|
|
<IconButton onClick={() => setShowResetDialog(true)}>
|
|
<VpnKey />
|
|
</IconButton>
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Löschen">
|
|
<IconButton
|
|
color="error"
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
>
|
|
<Delete />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Success/Error Messages */}
|
|
{successMessage && (
|
|
<Alert severity="success" sx={{ mb: 2 }}>
|
|
{successMessage}
|
|
</Alert>
|
|
)}
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
</Box>
|
|
|
|
<Grid container spacing={3}>
|
|
{/* Main Content */}
|
|
<Grid item xs={12} md={8}>
|
|
{/* Application Details */}
|
|
<Paper sx={{ p: 3, mb: 3 }}>
|
|
<Typography variant="h5" gutterBottom>
|
|
Antragsinformationen
|
|
</Typography>
|
|
|
|
<Grid container spacing={2}>
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Antragssteller
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.firstName} {formData.lastName}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
E-Mail
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.email ? (
|
|
<a
|
|
href={`mailto:${formData.email}`}
|
|
style={{ color: "inherit" }}
|
|
>
|
|
{formData.email}
|
|
</a>
|
|
) : (
|
|
"-"
|
|
)}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Telefon
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.phone ? (
|
|
<a
|
|
href={`tel:${formData.phone}`}
|
|
style={{ color: "inherit" }}
|
|
>
|
|
{formData.phone}
|
|
</a>
|
|
) : (
|
|
"-"
|
|
)}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Rolle
|
|
</Typography>
|
|
<Typography variant="body1">{formData.role || "-"}</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Institution
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.institutionName || "-"}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Fakultät
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.course || "-"}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12}>
|
|
<Divider sx={{ my: 2 }} />
|
|
</Grid>
|
|
|
|
<Grid item xs={12}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Projektname
|
|
</Typography>
|
|
<Typography variant="h6">{formData.projectName}</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Projektbeschreibung
|
|
</Typography>
|
|
<Typography variant="body1" sx={{ whiteSpace: "pre-line" }}>
|
|
{formData.description}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Startdatum
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.startDate
|
|
? dayjs(formData.startDate).format("DD.MM.YYYY")
|
|
: "-"}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Enddatum
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.endDate
|
|
? dayjs(formData.endDate).format("DD.MM.YYYY")
|
|
: "-"}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Teilnehmerzahl
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{formData.participants || "-"}
|
|
</Typography>
|
|
</Grid>
|
|
|
|
<Grid item xs={12} sm={6}>
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
Beantragte Summe
|
|
</Typography>
|
|
<Typography variant="h6" color="primary">
|
|
{formData.requestedAmountEur?.toLocaleString("de-DE", {
|
|
style: "currency",
|
|
currency: "EUR",
|
|
}) || "0,00 €"}
|
|
</Typography>
|
|
</Grid>
|
|
</Grid>
|
|
</Paper>
|
|
|
|
{/* Financing Information */}
|
|
{(formData.vsmFinancing || formData.qsmFinancing) && (
|
|
<Paper sx={{ p: 3, mb: 3 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
{currentApplication.variant === "QSM"
|
|
? "Erfüllte Aufgabe der QSM"
|
|
: "Erfüllte Aufgabe der VS nach §65 des Landeshochschulgesetzes"}
|
|
</Typography>
|
|
<Typography variant="body1">
|
|
{currentApplication.variant === "QSM" &&
|
|
(formData.financingCode || formData.qsmFinancing)
|
|
? QSM_FINANCING_LABELS[
|
|
(formData.financingCode ||
|
|
formData.qsmFinancing) as keyof typeof QSM_FINANCING_LABELS
|
|
] ||
|
|
formData.financingCode ||
|
|
formData.qsmFinancing
|
|
: currentApplication.variant === "VSM" &&
|
|
(formData.financingCode || formData.vsmFinancing)
|
|
? VSM_FINANCING_LABELS[
|
|
(formData.financingCode ||
|
|
formData.vsmFinancing) as keyof typeof VSM_FINANCING_LABELS
|
|
] ||
|
|
formData.financingCode ||
|
|
formData.vsmFinancing
|
|
: "-"}
|
|
</Typography>
|
|
|
|
{/* Display flags */}
|
|
{formData.vsmFlags && currentApplication.variant === "VSM" && (
|
|
<Box sx={{ mt: 2 }}>
|
|
<Typography
|
|
variant="subtitle2"
|
|
color="text.secondary"
|
|
gutterBottom
|
|
>
|
|
Zusätzliche Angaben:
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Die Maßnahme erfüllt Aufgaben der verfassten
|
|
Studierendenschaft:{" "}
|
|
{formData.vsmFlags.aufgaben ? "Ja" : "Nein"}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Es werden keine Einzelpersonen von der Maßnahme gefördert:{" "}
|
|
{formData.vsmFlags.individuell ? "Ja" : "Nein"}
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
|
|
{formData.qsmFlags && currentApplication.variant === "QSM" && (
|
|
<Box sx={{ mt: 2 }}>
|
|
<Typography
|
|
variant="subtitle2"
|
|
color="text.secondary"
|
|
gutterBottom
|
|
>
|
|
Zusätzliche Angaben:
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Es handelt sich um Stellenfinanzierungen:{" "}
|
|
{formData.qsmFlags.stellenfinanzierungen ? "Ja" : "Nein"}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Die Studierenden werden an der Planung und Durchführung
|
|
der Maßnahme beteiligt:{" "}
|
|
{formData.qsmFlags.studierende ? "Ja" : "Nein"}
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Es werden keine Einzelpersonen von der Maßnahme gefördert:{" "}
|
|
{formData.qsmFlags.individuell ? "Ja" : "Nein"}
|
|
</Typography>
|
|
{formData.qsmFlags.exkursionGenehmigt !== undefined && (
|
|
<Typography variant="body2">
|
|
• Die beantragte Exkursion wurde von den zuständigen
|
|
Stellen genehmigt:{" "}
|
|
{formData.qsmFlags.exkursionGenehmigt ? "Ja" : "Nein"}
|
|
</Typography>
|
|
)}
|
|
{formData.qsmFlags.exkursionBezuschusst !== undefined && (
|
|
<Typography variant="body2">
|
|
• Die Exkursion wird bereits aus anderen Mitteln
|
|
bezuschusst:{" "}
|
|
{formData.qsmFlags.exkursionBezuschusst ? "Ja" : "Nein"}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
)}
|
|
|
|
{/* Attachments */}
|
|
<Box sx={{ mt: 2 }}>
|
|
<Typography
|
|
variant="subtitle2"
|
|
color="text.secondary"
|
|
gutterBottom
|
|
>
|
|
Anhänge:
|
|
</Typography>
|
|
<Typography variant="body2">
|
|
• Vergleichsangebote liegen bei:{" "}
|
|
{formData.comparativeOffers ? "Ja" : "Nein"}
|
|
</Typography>
|
|
{currentApplication.variant === "QSM" && (
|
|
<Typography variant="body2">
|
|
• Die Fakultät ist über den Antrag informiert:{" "}
|
|
{formData.fakultaet ? "Ja" : "Nein"}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Paper>
|
|
)}
|
|
|
|
{/* Costs Breakdown */}
|
|
{formData.costs && formData.costs.length > 0 && (
|
|
<Paper sx={{ p: 3, mb: 3 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
Kostenpositionen
|
|
</Typography>
|
|
{formData.costs
|
|
.filter((cost) => cost.amountEur && cost.amountEur > 0)
|
|
.map((cost, index) => (
|
|
<Box
|
|
key={index}
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
py: 1,
|
|
}}
|
|
>
|
|
<Typography variant="body2">{cost.name}</Typography>
|
|
<Typography variant="body2" sx={{ fontWeight: "medium" }}>
|
|
{cost.amountEur?.toLocaleString("de-DE", {
|
|
style: "currency",
|
|
currency: "EUR",
|
|
}) || "0,00 €"}
|
|
</Typography>
|
|
</Box>
|
|
))}
|
|
<Divider sx={{ my: 1 }} />
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "space-between",
|
|
fontWeight: "bold",
|
|
}}
|
|
>
|
|
<Typography variant="subtitle1" color="text.primary">
|
|
Gesamt:
|
|
</Typography>
|
|
<Typography variant="subtitle1" color="primary">
|
|
{formData.requestedAmountEur?.toLocaleString("de-DE", {
|
|
style: "currency",
|
|
currency: "EUR",
|
|
}) || "0,00 €"}
|
|
</Typography>
|
|
</Box>
|
|
</Paper>
|
|
)}
|
|
</Grid>
|
|
|
|
{/* Sidebar */}
|
|
<Grid item xs={12} md={4}>
|
|
{/* Status Card */}
|
|
<Card sx={{ mb: 3 }}>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Status & Zeitstempel
|
|
</Typography>
|
|
<Box sx={{ mb: 2 }}>
|
|
{editingStatus ? (
|
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
|
<TextField
|
|
select
|
|
fullWidth
|
|
value={newStatus}
|
|
onChange={(e) => setNewStatus(e.target.value)}
|
|
size="small"
|
|
>
|
|
<MenuItem value="new">Neu</MenuItem>
|
|
<MenuItem value="in-review">In Prüfung</MenuItem>
|
|
<MenuItem value="approved">Genehmigt</MenuItem>
|
|
<MenuItem value="rejected">Abgelehnt</MenuItem>
|
|
</TextField>
|
|
<Button
|
|
size="small"
|
|
onClick={handleStatusChange}
|
|
startIcon={<Save />}
|
|
>
|
|
Speichern
|
|
</Button>
|
|
</Box>
|
|
) : (
|
|
<Chip
|
|
label={translateStatus(currentApplication.status)}
|
|
color={getStatusColor(currentApplication.status)}
|
|
onClick={() => setEditingStatus(true)}
|
|
sx={{ cursor: "pointer" }}
|
|
/>
|
|
)}
|
|
</Box>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Erstellt:{" "}
|
|
{dayjs(currentApplication.created_at).format(
|
|
"DD.MM.YYYY HH:mm",
|
|
)}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Zuletzt geändert:{" "}
|
|
{dayjs(currentApplication.updated_at).format(
|
|
"DD.MM.YYYY HH:mm",
|
|
)}
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Participating Faculties */}
|
|
{formData.participatingFaculties && (
|
|
<Card sx={{ mb: 3 }}>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Teilnehmende Fakultäten
|
|
</Typography>
|
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
|
|
{Object.entries(formData.participatingFaculties).map(
|
|
([faculty, participating]) =>
|
|
participating ? (
|
|
<Chip
|
|
key={faculty}
|
|
label={faculty.toUpperCase()}
|
|
size="small"
|
|
color="primary"
|
|
variant="outlined"
|
|
/>
|
|
) : null,
|
|
)}
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Admin Actions */}
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Admin-Aktionen
|
|
</Typography>
|
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
startIcon={<Download />}
|
|
onClick={handleDownloadPdf}
|
|
>
|
|
PDF herunterladen
|
|
</Button>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
startIcon={<Edit />}
|
|
onClick={() => navigate(`/admin/applications/${paId}/edit`)}
|
|
>
|
|
Antrag bearbeiten
|
|
</Button>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
startIcon={<SwapVert />}
|
|
onClick={() => setEditingStatus(true)}
|
|
disabled={editingStatus}
|
|
>
|
|
Status ändern
|
|
</Button>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
color="error"
|
|
startIcon={<Delete />}
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
>
|
|
Antrag löschen
|
|
</Button>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
<Dialog
|
|
open={showDeleteDialog}
|
|
onClose={() => setShowDeleteDialog(false)}
|
|
>
|
|
<DialogTitle>Antrag löschen</DialogTitle>
|
|
<DialogContent>
|
|
<Typography>
|
|
Sind Sie sicher, dass Sie den Antrag {paId} unwiderruflich löschen
|
|
möchten? Diese Aktion kann nicht rückgängig gemacht werden.
|
|
</Typography>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setShowDeleteDialog(false)}>Abbrechen</Button>
|
|
<Button
|
|
onClick={() => {
|
|
handleDelete();
|
|
setShowDeleteDialog(false);
|
|
}}
|
|
color="error"
|
|
variant="contained"
|
|
>
|
|
Löschen
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{/* Reset Credentials Dialog */}
|
|
<Dialog
|
|
open={showResetDialog}
|
|
onClose={() => {
|
|
setShowResetDialog(false);
|
|
setNewCredentials(null);
|
|
setCopiedField(null);
|
|
}}
|
|
>
|
|
<DialogTitle>Zugangsdaten zurücksetzen</DialogTitle>
|
|
<DialogContent>
|
|
{!newCredentials ? (
|
|
<Typography>
|
|
Möchten Sie neue Zugangsdaten für den Antrag {paId} generieren?
|
|
Die alten Zugangsdaten werden ungültig.
|
|
</Typography>
|
|
) : (
|
|
<Box>
|
|
<Alert severity="success" sx={{ mb: 2 }}>
|
|
Neue Zugangsdaten wurden generiert!
|
|
</Alert>
|
|
<Paper
|
|
variant="outlined"
|
|
sx={{
|
|
p: 2,
|
|
backgroundColor: (theme) =>
|
|
theme.palette.mode === "dark"
|
|
? theme.palette.grey[900]
|
|
: theme.palette.grey[50],
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="subtitle2"
|
|
color="text.secondary"
|
|
gutterBottom
|
|
>
|
|
Antrags-ID:
|
|
</Typography>
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 1,
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<Typography variant="body1" sx={{ fontFamily: "monospace" }}>
|
|
{newCredentials.pa_id}
|
|
</Typography>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => copyToClipboard(newCredentials.pa_id, "id")}
|
|
>
|
|
<ContentCopy fontSize="small" />
|
|
</IconButton>
|
|
{copiedField === "id" && (
|
|
<Typography variant="caption" color="success.main">
|
|
Kopiert!
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
|
|
<Typography
|
|
variant="subtitle2"
|
|
color="text.secondary"
|
|
gutterBottom
|
|
>
|
|
Neuer Zugangsschlüssel:
|
|
</Typography>
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: 1,
|
|
}}
|
|
>
|
|
<Typography variant="body1" sx={{ fontFamily: "monospace" }}>
|
|
{newCredentials.pa_key}
|
|
</Typography>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() =>
|
|
copyToClipboard(newCredentials.pa_key, "key")
|
|
}
|
|
>
|
|
<ContentCopy fontSize="small" />
|
|
</IconButton>
|
|
{copiedField === "key" && (
|
|
<Typography variant="caption" color="success.main">
|
|
Kopiert!
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Paper>
|
|
<Alert severity="warning">
|
|
Bitte speichern Sie diese Zugangsdaten sicher ab und teilen Sie
|
|
sie dem Antragsteller mit.
|
|
</Alert>
|
|
</Box>
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
onClick={() => {
|
|
setShowResetDialog(false);
|
|
setNewCredentials(null);
|
|
setCopiedField(null);
|
|
}}
|
|
>
|
|
{newCredentials ? "Schließen" : "Abbrechen"}
|
|
</Button>
|
|
{!newCredentials && (
|
|
<Button onClick={handleResetCredentials} variant="contained">
|
|
Zugangsdaten zurücksetzen
|
|
</Button>
|
|
)}
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Container>
|
|
);
|
|
};
|
|
|
|
export default AdminApplicationView;
|