feat: Add detail pages for Companies and Tokens
- Created CompanyDetail page with stats, info, and tokens list - Created TokenDetail page with click history and full tracking info - Added routes for /companies/:id and /tokens/:id - Made table rows clickable to navigate to detail pages - Added edit, delete, and mail resend functionality - Shows IP addresses, GeoIP location, device and browser info in click logs
This commit is contained in:
333
frontend/src/pages/CompanyDetail.jsx
Normal file
333
frontend/src/pages/CompanyDetail.jsx
Normal file
@@ -0,0 +1,333 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Paper,
|
||||
Typography,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
TextField,
|
||||
IconButton,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowBack,
|
||||
Edit,
|
||||
Delete,
|
||||
Token as TokenIcon,
|
||||
CheckCircle,
|
||||
TrendingUp,
|
||||
} from '@mui/icons-material';
|
||||
import { companyService } from '../services/companyService';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
function CompanyDetail() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [company, setCompany] = useState(null);
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [stats, setStats] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editDialog, setEditDialog] = useState(false);
|
||||
const [deleteDialog, setDeleteDialog] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
industry: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [companyData, tokensData, statsData] = await Promise.all([
|
||||
companyService.getById(id),
|
||||
companyService.getTokens(id),
|
||||
companyService.getStats(id),
|
||||
]);
|
||||
setCompany(companyData.data);
|
||||
setTokens(tokensData.data);
|
||||
setStats(statsData.data);
|
||||
setFormData({
|
||||
name: companyData.data.name,
|
||||
description: companyData.data.description || '',
|
||||
industry: companyData.data.industry || '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load company:', error);
|
||||
alert('Şirket yüklenemedi');
|
||||
navigate('/companies');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async () => {
|
||||
try {
|
||||
await companyService.update(id, formData);
|
||||
setEditDialog(false);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
console.error('Failed to update company:', error);
|
||||
alert('Şirket güncellenemedi');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await companyService.delete(id);
|
||||
navigate('/companies');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete company:', error);
|
||||
alert('Şirket silinemedi: Önce tokenları silmelisiniz');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Header */}
|
||||
<Box display="flex" alignItems="center" mb={3}>
|
||||
<IconButton onClick={() => navigate('/companies')} sx={{ mr: 2 }}>
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
<Typography variant="h4" sx={{ flexGrow: 1 }}>
|
||||
{company.name}
|
||||
</Typography>
|
||||
<Button
|
||||
startIcon={<Edit />}
|
||||
onClick={() => setEditDialog(true)}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
Düzenle
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<Delete />}
|
||||
color="error"
|
||||
onClick={() => setDeleteDialog(true)}
|
||||
>
|
||||
Sil
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<Grid container spacing={3} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
Toplam Token
|
||||
</Typography>
|
||||
<Typography variant="h4">{stats.total_tokens}</Typography>
|
||||
</Box>
|
||||
<TokenIcon color="primary" sx={{ fontSize: 40 }} />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
Toplam Tıklama
|
||||
</Typography>
|
||||
<Typography variant="h4">{stats.total_clicks}</Typography>
|
||||
</Box>
|
||||
<CheckCircle color="success" sx={{ fontSize: 40 }} />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
Başarı Oranı
|
||||
</Typography>
|
||||
<Typography variant="h4">{stats.click_rate}%</Typography>
|
||||
</Box>
|
||||
<TrendingUp
|
||||
color={stats.click_rate > 30 ? 'error' : 'warning'}
|
||||
sx={{ fontSize: 40 }}
|
||||
/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Company Info */}
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Şirket Bilgileri
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Sektör
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
{company.industry || 'Belirtilmemiş'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Oluşturulma Tarihi
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
{format(new Date(company.created_at), 'dd/MM/yyyy HH:mm')}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{company.description && (
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Açıklama
|
||||
</Typography>
|
||||
<Typography variant="body1">{company.description}</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
|
||||
{/* Tokens Table */}
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Tokenlar ({tokens.length})
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell>Çalışan</TableCell>
|
||||
<TableCell>Durum</TableCell>
|
||||
<TableCell align="right">Tıklama</TableCell>
|
||||
<TableCell>Tarih</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tokens.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} align="center">
|
||||
<Typography color="textSecondary">
|
||||
Henüz token oluşturulmamış
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
tokens.map((token) => (
|
||||
<TableRow key={token.id} hover>
|
||||
<TableCell>{token.target_email}</TableCell>
|
||||
<TableCell>{token.employee_name || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={token.clicked ? 'Tıklandı' : 'Bekliyor'}
|
||||
color={token.clicked ? 'success' : 'default'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="right">{token.click_count}×</TableCell>
|
||||
<TableCell>
|
||||
{format(new Date(token.created_at), 'dd/MM/yyyy HH:mm')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
|
||||
{/* Edit Dialog */}
|
||||
<Dialog open={editDialog} onClose={() => setEditDialog(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Şirket Düzenle</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Şirket Adı"
|
||||
fullWidth
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Açıklama"
|
||||
fullWidth
|
||||
multiline
|
||||
rows={2}
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Sektör"
|
||||
fullWidth
|
||||
value={formData.industry}
|
||||
onChange={(e) => setFormData({ ...formData, industry: e.target.value })}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setEditDialog(false)}>İptal</Button>
|
||||
<Button onClick={handleUpdate} variant="contained">
|
||||
Güncelle
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
<Dialog open={deleteDialog} onClose={() => setDeleteDialog(false)}>
|
||||
<DialogTitle>Şirketi Sil?</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
<strong>{company.name}</strong> şirketini silmek istediğinizden emin misiniz?
|
||||
Bu işlem geri alınamaz.
|
||||
</Typography>
|
||||
{tokens.length > 0 && (
|
||||
<Typography color="error" sx={{ mt: 2 }}>
|
||||
⚠️ Bu şirkete ait {tokens.length} token var. Önce tokenları silmelisiniz.
|
||||
</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteDialog(false)}>İptal</Button>
|
||||
<Button onClick={handleDelete} color="error" variant="contained">
|
||||
Sil
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CompanyDetail;
|
||||
|
||||
336
frontend/src/pages/TokenDetail.jsx
Normal file
336
frontend/src/pages/TokenDetail.jsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Paper,
|
||||
Typography,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ArrowBack,
|
||||
Delete,
|
||||
Send,
|
||||
LocationOn,
|
||||
Computer,
|
||||
AccessTime,
|
||||
CheckCircle,
|
||||
Cancel,
|
||||
} from '@mui/icons-material';
|
||||
import { tokenService } from '../services/tokenService';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
function TokenDetail() {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [token, setToken] = useState(null);
|
||||
const [clicks, setClicks] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deleteDialog, setDeleteDialog] = useState(false);
|
||||
const [sendingMail, setSendingMail] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [tokenData, clicksData] = await Promise.all([
|
||||
tokenService.getById(id),
|
||||
tokenService.getClicks(id),
|
||||
]);
|
||||
setToken(tokenData.data);
|
||||
setClicks(clicksData.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to load token:', error);
|
||||
alert('Token yüklenemedi');
|
||||
navigate('/tokens');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMail = async () => {
|
||||
setSendingMail(true);
|
||||
try {
|
||||
await tokenService.sendMail(id);
|
||||
alert('Mail başarıyla gönderildi!');
|
||||
} catch (error) {
|
||||
console.error('Failed to send mail:', error);
|
||||
alert('Mail gönderilemedi: ' + (error.response?.data?.error || error.message));
|
||||
} finally {
|
||||
setSendingMail(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await tokenService.delete(id);
|
||||
navigate('/tokens');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete token:', error);
|
||||
alert('Token silinemedi');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* Header */}
|
||||
<Box display="flex" alignItems="center" mb={3}>
|
||||
<IconButton onClick={() => navigate('/tokens')} sx={{ mr: 2 }}>
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Typography variant="h5">{token.target_email}</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{token.company?.name} - {token.employee_name || 'İsimsiz'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
startIcon={<Send />}
|
||||
onClick={handleSendMail}
|
||||
disabled={sendingMail}
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
{sendingMail ? 'Gönderiliyor...' : 'Mail Gönder'}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={<Delete />}
|
||||
color="error"
|
||||
onClick={() => setDeleteDialog(true)}
|
||||
>
|
||||
Sil
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<Grid container spacing={3} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{token.clicked ? (
|
||||
<CheckCircle color="success" sx={{ fontSize: 40 }} />
|
||||
) : (
|
||||
<Cancel color="disabled" sx={{ fontSize: 40 }} />
|
||||
)}
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
Durum
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
{token.clicked ? 'Tıklandı' : 'Bekliyor'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<AccessTime color="primary" sx={{ fontSize: 40 }} />
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2">
|
||||
Tıklama Sayısı
|
||||
</Typography>
|
||||
<Typography variant="h6">{token.click_count}×</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2" gutterBottom>
|
||||
Oluşturulma
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
{format(new Date(token.created_at), 'dd/MM/yyyy')}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{format(new Date(token.created_at), 'HH:mm')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Box>
|
||||
<Typography color="textSecondary" variant="body2" gutterBottom>
|
||||
İlk Tıklama
|
||||
</Typography>
|
||||
{token.first_clicked_at ? (
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
{format(new Date(token.first_clicked_at), 'dd/MM/yyyy')}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{format(new Date(token.first_clicked_at), 'HH:mm')}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Henüz tıklanmadı
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Token Info */}
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Token Bilgileri
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Token
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ fontFamily: 'monospace' }}>
|
||||
{token.token}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Tracking URL
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}
|
||||
>
|
||||
{`${window.location.origin.replace('5173', '3000')}/t/${token.token}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Şablon Tipi
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
{token.template_type || 'Belirtilmemiş'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Şirket
|
||||
</Typography>
|
||||
<Typography variant="body1">{token.company?.name}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
|
||||
{/* Click History */}
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Tıklama Geçmişi ({clicks.length})
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Zaman</TableCell>
|
||||
<TableCell>IP Adresi</TableCell>
|
||||
<TableCell>Konum</TableCell>
|
||||
<TableCell>Cihaz</TableCell>
|
||||
<TableCell>Tarayıcı</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{clicks.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} align="center">
|
||||
<Typography color="textSecondary">
|
||||
Henüz tıklama kaydı yok
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
clicks.map((click) => (
|
||||
<TableRow key={click.id} hover>
|
||||
<TableCell>
|
||||
{format(new Date(click.clicked_at), 'dd/MM/yyyy HH:mm:ss')}
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontFamily: 'monospace', fontSize: '0.875rem' }}>
|
||||
{click.ip_address}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box display="flex" alignItems="center" gap={0.5}>
|
||||
<LocationOn fontSize="small" color="action" />
|
||||
{click.city && click.country
|
||||
? `${click.city}, ${click.country}`
|
||||
: 'Bilinmiyor'}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box display="flex" alignItems="center" gap={0.5}>
|
||||
<Computer fontSize="small" color="action" />
|
||||
{click.device || 'Bilinmiyor'}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>{click.browser || 'Bilinmiyor'}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
<Dialog open={deleteDialog} onClose={() => setDeleteDialog(false)}>
|
||||
<DialogTitle>Token'ı Sil?</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>
|
||||
<strong>{token.target_email}</strong> için oluşturulan bu token'ı silmek
|
||||
istediğinizden emin misiniz? Bu işlem geri alınamaz.
|
||||
</Typography>
|
||||
{clicks.length > 0 && (
|
||||
<Typography color="warning.main" sx={{ mt: 2 }}>
|
||||
⚠️ Bu token'a ait {clicks.length} tıklama kaydı da silinecek.
|
||||
</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteDialog(false)}>İptal</Button>
|
||||
<Button onClick={handleDelete} color="error" variant="contained">
|
||||
Sil
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TokenDetail;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -26,6 +27,7 @@ import { templateService } from '../services/templateService';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
function Tokens() {
|
||||
const navigate = useNavigate();
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [companies, setCompanies] = useState([]);
|
||||
const [templates, setTemplates] = useState([]);
|
||||
@@ -107,7 +109,12 @@ function Tokens() {
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{tokens.map((token) => (
|
||||
<TableRow key={token.id} hover sx={{ cursor: 'pointer' }}>
|
||||
<TableRow
|
||||
key={token.id}
|
||||
hover
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => navigate(`/tokens/${token.id}`)}
|
||||
>
|
||||
<TableCell>{token.target_email}</TableCell>
|
||||
<TableCell>{token.company?.name}</TableCell>
|
||||
<TableCell>{token.employee_name || '-'}</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user