redesigned admin for website

This commit is contained in:
nvrl committed 2026-02-18 03:10:57 +01:00
1 parent 4e7c11c38b
commit e681b74533
9 files changed
+368 -33

No files matched your search

+36 -9
View File
@@ -57,6 +57,8 @@ const Settings = lazy(() => import('./pages/Settings'));
const Support = lazy(() => import('./pages/Support'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail'));
const AdminRatingDetail = lazy(() => import('./pages/AdminRatingDetail'));
const AdminReplyDetail = lazy(() => import('./pages/AdminReplyDetail'));
interface SearchResult {
id: number;
@@ -215,8 +217,15 @@ const App: React.FC = () => {
const [loadingUser, setLoadingUser] = useState(true);
const [drawerOpen, setDrawerOpen] = useState(false);
const [catOpen, setCatOpen] = useState(false);
const [adminMode, setAdminMode] = useState(localStorage.getItem('admin-mode') === 'true');
const isMobile = useMediaQuery('(max-width:900px)');
const toggleAdminMode = () => {
const newVal = !adminMode;
setAdminMode(newVal);
localStorage.setItem('admin-mode', String(newVal));
};
const fetchNotifications = async () => {
try {
const res = await api.get('notifications/');
@@ -403,7 +412,19 @@ const App: React.FC = () => {
{user.is_superuser && (
<ListItem disablePadding>
<ListItemButton component={Link} to="/admin" onClick={() => setDrawerOpen(false)}>
<ListItemButton onClick={toggleAdminMode}>
<ListItemText
primary={t('admin.adminMode')}
secondary={adminMode ? "ON" : "OFF"}
sx={{ color: adminMode ? 'primary.main' : 'inherit' }}
/>
</ListItemButton>
</ListItem>
)}
{user.is_superuser && (
<ListItem disablePadding>
<ListItemButton component={Link} to="/admin-panel" onClick={() => setDrawerOpen(false)}>
<ListItemText primary="Admin Panel" sx={{ color: 'primary.main', fontWeight: 'bold' }} />
</ListItemButton>
</ListItem>
@@ -594,9 +615,13 @@ const App: React.FC = () => {
<MenuItem component={Link} to="/settings" onClick={() => setAnchorEl(null)}>{t('nav.settings')}</MenuItem>
<MenuItem component={Link} to="/support" onClick={() => setAnchorEl(null)}>{t('support.title')}</MenuItem>
{user.is_superuser && (
<MenuItem component={Link} to="/admin" onClick={() => setAnchorEl(null)} sx={{ fontWeight: 'bold', color: 'primary.main' }}>Admin Panel</MenuItem>
<MenuItem onClick={toggleAdminMode} sx={{ color: adminMode ? 'primary.main' : 'inherit' }}>
{t('admin.adminMode')}: {adminMode ? "ON" : "OFF"}
</MenuItem>
)}
<Divider />
{user.is_superuser && (
<MenuItem component={Link} to="/admin-panel" onClick={() => setAnchorEl(null)} sx={{ fontWeight: 'bold', color: 'primary.main' }}>Admin Panel</MenuItem>
)} <Divider />
<MenuItem onClick={handleLogout} sx={{ color: 'error.main' }}>{t('nav.logout')}</MenuItem> </Menu>
</Box>
) : (
@@ -631,18 +656,20 @@ const App: React.FC = () => {
<Box sx={{ flexGrow: 1, width: '100%' }}>
<Suspense fallback={<Box sx={{ display: 'flex', justifyContent: 'center', mt: 8 }}><CircularProgress /></Box>}>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/" element={<MainPage adminMode={adminMode} />} />
<Route path="/category/:slug" element={<CategoryFlavors />} />
<Route path="/flavor/:id" element={<FlavorDetail />} />
<Route path="/profile/:username" element={<PublicProfile />} />
<Route path="/community" element={<CommunityFeed />} />
<Route path="/flavor/:id" element={<FlavorDetail adminMode={adminMode} />} />
<Route path="/profile/:username" element={<PublicProfile adminMode={adminMode} />} />
<Route path="/community" element={<CommunityFeed adminMode={adminMode} />} />
<Route path="/about" element={<About />} />
<Route path="/privacy" element={<Privacy />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings themeName={themeName} onThemeChange={handleThemeChange} />} />
<Route path="/support" element={<Support />} />
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/admin/user/:id" element={<AdminUserDetail />} />
<Route path="/admin-panel" element={<AdminDashboard />} />
<Route path="/admin-panel/user/:id" element={<AdminUserDetail />} />
<Route path="/admin-panel/rating/:id" element={<AdminRatingDetail />} />
<Route path="/admin-panel/reply/:id" element={<AdminReplyDetail />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<Box sx={{ p: 4, textAlign: 'center' }}><Typography variant="h5">404 - Page Not Found</Typography><Button component={Link} to="/" sx={{ mt: 2 }}>Back to Home</Button></Box>} />
</Routes>
+8 -2
View File
@@ -156,6 +156,9 @@ const resources = {
"testEmailSuccess": "Test email sent successfully!",
"ipHistory": "Login IP History",
"manageUser": "Manage User",
"manageRating": "Manage Rating",
"manageReply": "Manage Reply",
"adminMode": "Admin Mode",
"deactivate": "Deactivate Account",
"activate": "Activate Account",
"dangerZone": "Danger Zone",
@@ -342,8 +345,11 @@ const resources = {
"sendTestEmail": "Test-E-Mail senden",
"testEmailSuccess": "Test-E-Mail erfolgreich gesendet!",
"ipHistory": "Login-IP-Verlauf",
"manageUser": "Benutzer verwalten",
"deactivate": "Konto deaktivieren",
"manageUser": "Manage User",
"manageRating": "Manage Rating",
"manageReply": "Manage Reply",
"adminMode": "Admin Mode",
"deactivate": "Deactivate Account",
"activate": "Konto aktivieren",
"dangerZone": "Gefahrenzone",
"deleteUser": "Benutzer dauerhaft löschen"
+44 -18
View File
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
import {
Container, Typography, Box, Grid, Card, CardContent, Button,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
Paper, Stack, alpha, CircularProgress, Chip, TextField, IconButton
Paper, Stack, alpha, CircularProgress, Chip, TextField, IconButton,
useMediaQuery
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import EmailIcon from '@mui/icons-material/Email';
@@ -40,6 +41,7 @@ interface AdminUser {
const AdminDashboard: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const isMobile = useMediaQuery('(max-width:900px)');
useTitle(t('admin.title'));
const [stats, setStats] = useState<Stats | null>(null);
const [users, setUsers] = useState<AdminUser[]>([]);
@@ -84,38 +86,38 @@ const AdminDashboard: React.FC = () => {
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Typography variant="h3" sx={{ fontWeight: 'bold', mb: 4 }}>{t('admin.title')}</Typography>
<Typography variant={isMobile ? "h4" : "h3"} sx={{ fontWeight: 'bold', mb: 4 }}>{t('admin.title')}</Typography>
<Grid container spacing={3} sx={{ mb: 6 }}>
<Grid size={{ xs: 12, md: 3 }}>
<Grid container spacing={isMobile ? 2 : 3} sx={{ mb: 6 }}>
<Grid size={{ xs: 6, md: 3 }}>
<Card sx={{ bgcolor: (theme) => alpha(theme.palette.primary.main, 0.1) }}>
<CardContent>
<Typography variant="overline">{t('admin.totalUsers')}</Typography>
<Typography variant="h4" sx={{ fontWeight: 'bold' }}>{stats?.total_users}</Typography>
<CardContent sx={{ p: isMobile ? 2 : 3 }}>
<Typography variant="overline" sx={{ fontSize: '0.6rem' }}>{t('admin.totalUsers')}</Typography>
<Typography variant={isMobile ? "h5" : "h4"} sx={{ fontWeight: 'bold' }}>{stats?.total_users}</Typography>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 3 }}>
<Grid size={{ xs: 6, md: 3 }}>
<Card sx={{ bgcolor: (theme) => alpha(theme.palette.error.main, 0.1) }}>
<CardContent>
<Typography variant="overline">{t('admin.openTickets')}</Typography>
<Typography variant="h4" sx={{ fontWeight: 'bold', color: 'error.main' }}>{stats?.open_tickets}</Typography>
<CardContent sx={{ p: isMobile ? 2 : 3 }}>
<Typography variant="overline" sx={{ fontSize: '0.6rem' }}>{t('admin.openTickets')}</Typography>
<Typography variant={isMobile ? "h5" : "h4"} sx={{ fontWeight: 'bold', color: 'error.main' }}>{stats?.open_tickets}</Typography>
</CardContent>
</Card>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Card variant="outlined">
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<CardContent sx={{ p: isMobile ? 2 : 3 }}>
<Stack direction={isMobile ? "column" : "row"} justifyContent="space-between" alignItems={isMobile ? "flex-start" : "center"} spacing={2}>
<Box>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>{t('admin.config')}: SMTP</Typography>
<Typography variant="caption" color="text.secondary">
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
{stats?.email_config.host}:{stats?.email_config.port} •
TLS: {String(stats?.email_config.use_tls)} •
Insecure: {String(stats?.email_config.skip_verify)}
</Typography>
</Box>
<Button variant="outlined" startIcon={<EmailIcon />} onClick={handleSendTestEmail}>
<Button variant="outlined" fullWidth={isMobile} startIcon={<EmailIcon />} onClick={handleSendTestEmail} size="small">
{t('admin.sendTestEmail')}
</Button>
</Stack>
@@ -125,14 +127,37 @@ const AdminDashboard: React.FC = () => {
</Grid>
<Box sx={{ mb: 4 }}>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
<Stack direction={isMobile ? "column" : "row"} justifyContent="space-between" alignItems={isMobile ? "flex-start" : "center"} sx={{ mb: 2 }} spacing={2}>
<Typography variant="h5" sx={{ fontWeight: 'bold' }}>{t('admin.users')}</Typography>
<TextField
size="small" placeholder="Search..."
fullWidth={isMobile}
value={search} onChange={(e) => setSearch(e.target.value)}
sx={{ width: 300 }}
sx={{ width: isMobile ? '100%' : 300 }}
/>
</Stack>
{isMobile ? (
<Stack spacing={2}>
{filteredUsers.map(user => (
<Card key={user.id} variant="outlined" sx={{ borderRadius: 3 }}>
<CardContent sx={{ p: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>{user.username}</Typography>
<IconButton size="small" onClick={() => navigate(`/admin-panel/user/${user.id}`)}>
<EditIcon fontSize="small" />
</IconButton>
</Box>
<Typography variant="caption" color="text.secondary" display="block">{user.email}</Typography>
<Stack direction="row" spacing={1} sx={{ mt: 1 }}>
<Chip label={user.is_active ? 'Active' : 'Inactive'} color={user.is_active ? 'success' : 'default'} size="small" sx={{ height: 20, fontSize: '0.65rem' }} />
{user.is_superuser && <Chip label="Admin" color="secondary" size="small" sx={{ height: 20, fontSize: '0.65rem' }} />}
</Stack>
</CardContent>
</Card>
))}
</Stack>
) : (
<TableContainer component={Paper} sx={{ borderRadius: 3, border: '1px solid', borderColor: 'divider' }}>
<Table>
<TableHead sx={{ bgcolor: 'action.hover' }}>
@@ -172,7 +197,7 @@ const AdminDashboard: React.FC = () => {
</Stack>
</TableCell>
<TableCell align="right">
<IconButton size="small" onClick={() => navigate(`/admin/user/${user.id}`)}>
<IconButton size="small" onClick={() => navigate(`/admin-panel/user/${user.id}`)}>
<EditIcon fontSize="small" />
</IconButton>
</TableCell>
@@ -181,6 +206,7 @@ const AdminDashboard: React.FC = () => {
</TableBody>
</Table>
</TableContainer>
)}
</Box>
</Container>
);
+98
View File
@@ -0,0 +1,98 @@
import React, { useState, useEffect } from 'react';
import {
Container, Typography, Box, Button,
Stack, CircularProgress, TextField, Paper
} from '@mui/material';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import DeleteIcon from '@mui/icons-material/Delete';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
import api from '../api';
import { useTitle } from '../hooks/useTitle';
import MentionTextField from '../components/MentionTextField';
const AdminRatingDetail: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
const [score, setScore] = useState(0);
const [comment, setComment] = useState('');
const [rating, setRating] = useState<any>(null);
const [loading, setLoading] = useState(true);
const fetchRating = async () => {
try {
const res = await api.get(`ratings/${id}/`);
setRating(res.data);
setScore(res.data.score);
setComment(res.data.comment || '');
} catch (err) {
console.error(err);
navigate('/admin-panel');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchRating();
}, [id]);
useTitle(rating ? `${t('admin.manageRating')}: ${rating.flavor_name}` : 'Rating Management');
const handleUpdate = async () => {
try {
await api.patch(`ratings/${id}/`, { score, comment });
fetchRating();
alert('Rating updated!');
} catch (err) { alert('Update failed'); }
};
const handleDelete = async () => {
if (!window.confirm('Delete this rating and all replies?')) return;
try {
await api.delete(`ratings/${id}/`);
navigate('/admin-panel');
} catch (err) { alert('Delete failed'); }
};
if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', mt: 8 }}><CircularProgress /></Box>;
return (
<Container maxWidth="sm" sx={{ py: 4 }}>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)}
sx={{ mb: 4, borderRadius: 2 }}
>
{t('common.back')}
</Button>
<Typography variant="h4" sx={{ fontWeight: 'bold', mb: 4 }}>{t('admin.manageRating')}</Typography>
<Paper sx={{ p: 3, borderRadius: 4 }}>
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
{rating.flavor_name} (User: {rating.user})
</Typography>
<Stack spacing={3} sx={{ mt: 3 }}>
<TextField
label="Score" type="number"
slotProps={{ input: { inputProps: { min: 1, max: 10 } } }}
value={score} onChange={(e) => setScore(Number(e.target.value))}
/>
<MentionTextField
multiline rows={4}
value={comment} onChange={setComment}
/>
<Stack direction="row" spacing={2}>
<Button variant="contained" fullWidth onClick={handleUpdate}>{t('common.save')}</Button>
<Button variant="outlined" color="error" fullWidth startIcon={<DeleteIcon />} onClick={handleDelete}>{t('common.delete')}</Button>
</Stack>
</Stack>
</Paper>
</Container>
);
};
export default AdminRatingDetail;
+92
View File
@@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react';
import {
Container, Typography, Box, Button,
Stack, CircularProgress, Paper
} from '@mui/material';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import DeleteIcon from '@mui/icons-material/Delete';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
import api from '../api';
import { useTitle } from '../hooks/useTitle';
import MentionTextField from '../components/MentionTextField';
const AdminReplyDetail: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { id } = useParams<{ id: string }>();
const [text, setText] = useState('');
const [reply, setReply] = useState<any>(null);
const [loading, setLoading] = useState(true);
const fetchReply = async () => {
try {
// We use the replies list/detail endpoint
const res = await api.get(`replies/${id}/`);
setReply(res.data);
setText(res.data.text);
} catch (err) {
console.error(err);
navigate('/admin-panel');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchReply();
}, [id]);
useTitle(reply ? `${t('admin.manageReply')}` : 'Reply Management');
const handleUpdate = async () => {
try {
await api.patch(`replies/${id}/`, { text });
fetchReply();
alert('Reply updated!');
} catch (err) { alert('Update failed'); }
};
const handleDelete = async () => {
if (!window.confirm('Delete this reply?')) return;
try {
await api.delete(`replies/${id}/`);
navigate('/admin-panel');
} catch (err) { alert('Delete failed'); }
};
if (loading) return <Box sx={{ display: 'flex', justifyContent: 'center', mt: 8 }}><CircularProgress /></Box>;
return (
<Container maxWidth="sm" sx={{ py: 4 }}>
<Button
variant="outlined"
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)}
sx={{ mb: 4, borderRadius: 2 }}
>
{t('common.back')}
</Button>
<Typography variant="h4" sx={{ fontWeight: 'bold', mb: 4 }}>{t('admin.manageReply')}</Typography>
<Paper sx={{ p: 3, borderRadius: 4 }}>
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
Reply by {reply.user}
</Typography>
<Stack spacing={3} sx={{ mt: 3 }}>
<MentionTextField
multiline rows={4}
value={text} onChange={setText}
/>
<Stack direction="row" spacing={2}>
<Button variant="contained" fullWidth onClick={handleUpdate}>{t('common.save')}</Button>
<Button variant="outlined" color="error" fullWidth startIcon={<DeleteIcon />} onClick={handleDelete}>{t('common.delete')}</Button>
</Stack>
</Stack>
</Paper>
</Container>
);
};
export default AdminReplyDetail;
+25 -1
View File
@@ -79,7 +79,11 @@ interface Notification {
flavor_id: number | null;
}
const CommunityFeed: React.FC = () => {
interface CommunityFeedProps {
adminMode?: boolean;
}
const CommunityFeed: React.FC<CommunityFeedProps> = ({ adminMode }) => {
const { t } = useTranslation();
useTitle(t('community.title'));
const navigate = useNavigate();
@@ -274,6 +278,16 @@ const CommunityFeed: React.FC = () => {
<RatingBadge score={rating.score} />
</Box>
{adminMode && (
<Button
size="small" variant="outlined" color="secondary"
onClick={() => navigate(`/admin-panel/rating/${rating.id}`)}
sx={{ mb: 2, borderRadius: 2, textTransform: 'none', fontWeight: 'bold' }}
>
{t('admin.manageRating')}
</Button>
)}
{/* Flavor and Comment */}
<Box sx={{ display: 'flex', gap: 3 }}>
<Box sx={{ flex: 1 }}>
@@ -315,7 +329,17 @@ const CommunityFeed: React.FC = () => {
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', fontSize: '0.8rem' }}>{reply.user}</Typography>
<Typography variant="caption" color="text.secondary">{formatTimestamp(reply.created_at)}</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="body2"><RichText text={reply.text} /></Typography>
{adminMode && (
<Button
size="small" sx={{ minWidth: 0, py: 0, fontSize: '0.7rem' }}
onClick={() => navigate(`/admin-panel/reply/${reply.id}`)}
>
{t('admin.manageReply')}
</Button>
)}
</Box>
</Box>
))}
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
+26 -1
View File
@@ -22,6 +22,7 @@ import api from '../api';
import { useTitle } from '../hooks/useTitle';
import { formatDate } from '../utils/date';
import RatingBadge from '../components/RatingBadge';
import { useNavigate } from 'react-router-dom';
interface Flavor {
id: number;
@@ -47,8 +48,13 @@ interface Review {
flavor: number;
}
const MainPage: React.FC = () => {
interface MainPageProps {
adminMode?: boolean;
}
const MainPage: React.FC<MainPageProps> = ({ adminMode }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [topFlavors, setTopFlavors] = useState<Flavor[]>([]);
const [newestFlavors, setNewestFlavors] = useState<Flavor[]>([]);
const [recentReviews, setRecentReviews] = useState<Review[]>([]);
@@ -283,6 +289,16 @@ const MainPage: React.FC = () => {
</Typography>
</Box>
{adminMode && (
<Button
size="small" variant="outlined" color="secondary"
onClick={(e) => { e.preventDefault(); e.stopPropagation(); navigate(`/admin-panel/rating/${rating.id}`); }}
sx={{ mb: 1, borderRadius: 1, fontSize: '0.6rem', py: 0, textTransform: 'none' }}
>
{t('admin.manageRating')}
</Button>
)}
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<Box
component="img"
@@ -496,6 +512,15 @@ const MainPage: React.FC = () => {
<Box sx={{ flexGrow: 1 }} />
<RatingBadge score={review.score} size="small" />
</Box>
{adminMode && (
<Button
size="small" variant="text" color="secondary"
onClick={() => navigate(`/admin-panel/rating/${review.id}`)}
sx={{ mb: 1, fontSize: '0.65rem', py: 0, textTransform: 'none', fontWeight: 'bold' }}
>
{t('admin.manageRating')}
</Button>
)}
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
{formatDate(review.created_at)} • on <Link to={`/flavor/${review.flavor}`} style={{ color: 'inherit', fontWeight: 'bold' }}>{review.flavor_name}</Link>
</Typography>
+14 -1
View File
@@ -45,7 +45,11 @@ interface ProfileData {
ratings: Rating[];
}
const PublicProfile: React.FC = () => {
interface PublicProfileProps {
adminMode?: boolean;
}
const PublicProfile: React.FC<PublicProfileProps> = ({ adminMode }) => {
const { t } = useTranslation();
const { username } = useParams<{ username: string }>();
const navigate = useNavigate();
@@ -265,6 +269,15 @@ const PublicProfile: React.FC = () => {
}}>
{rating.flavor_name}
</Typography>
{adminMode && (
<Button
size="small" fullWidth variant="text" color="secondary"
onClick={(e) => { e.preventDefault(); e.stopPropagation(); navigate(`/admin-panel/rating/${rating.id}`); }}
sx={{ fontSize: '0.6rem', py: 0, textTransform: 'none' }}
>
Manage
</Button>
)}
</CardContent>
</Link>
</Card>
+25 -1
View File
@@ -61,7 +61,11 @@ interface Flavor {
shop_url: string | null;
}
const FlavorDetail: React.FC = () => {
interface FlavorDetailProps {
adminMode?: boolean;
}
const FlavorDetail: React.FC<FlavorDetailProps> = ({ adminMode }) => {
const { t } = useTranslation();
const theme = useTheme();
const { id } = useParams<{ id: string }>();
@@ -403,7 +407,18 @@ const FlavorDetail: React.FC = () => {
</Typography>
</Box>
</Box>
<Stack direction="row" spacing={1} alignItems="center">
<RatingBadge score={rating.score} />
{adminMode && (
<Button
size="small" variant="outlined" color="secondary"
onClick={() => navigate(`/admin-panel/rating/${rating.id}`)}
sx={{ borderRadius: 2, textTransform: 'none', fontWeight: 'bold' }}
>
{t('admin.manageRating')}
</Button>
)}
</Stack>
</Box>
{rating.comment ? (
@@ -457,6 +472,15 @@ const FlavorDetail: React.FC = () => {
<Button size="small" color="error" sx={{ minWidth: 0, py: 0, fontSize: '0.7rem', fontWeight: 'bold' }} onClick={() => handleDeleteReply(reply.id)}>{t('common.delete')}</Button>
</Box>
)}
{adminMode && (
<Button
size="small" variant="text" color="secondary"
onClick={() => navigate(`/admin-panel/reply/${reply.id}`)}
sx={{ minWidth: 0, py: 0, fontSize: '0.7rem', fontWeight: 'bold' }}
>
{t('admin.manageReply')}
</Button>
)}
</Box>
{editingReplyId === reply.id ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>