Initial digitCRM project structure with backend and frontend
This commit is contained in:
206
frontend/client/src/pages/Campaigns.tsx
Normal file
206
frontend/client/src/pages/Campaigns.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { campaignsAPI } from '@/lib/api';
|
||||
import { toast } from 'sonner';
|
||||
import { Plus, Send, Trash2 } from 'lucide-react';
|
||||
import DashboardLayout from '@/components/DashboardLayout';
|
||||
|
||||
interface Campaign {
|
||||
CampaignID: number;
|
||||
CampaignName: string;
|
||||
Subject: string;
|
||||
Status: string;
|
||||
CreatedDate: string;
|
||||
SentDate?: string;
|
||||
}
|
||||
|
||||
export default function Campaigns() {
|
||||
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
campaignName: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchCampaigns();
|
||||
}, []);
|
||||
|
||||
const fetchCampaigns = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await campaignsAPI.getAll();
|
||||
setCampaigns(response.data);
|
||||
} catch (error) {
|
||||
toast.error('Грешка при зареждане на кампаниите');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await campaignsAPI.create(formData);
|
||||
toast.success('Кампанията е създадена');
|
||||
setOpen(false);
|
||||
setFormData({
|
||||
campaignName: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
});
|
||||
fetchCampaigns();
|
||||
} catch (error: any) {
|
||||
toast.error(error.response?.data?.error || 'Грешка при запазване');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = async (id: number) => {
|
||||
if (confirm('Сигурни ли сте, че искате да изпратите тази кампания?')) {
|
||||
try {
|
||||
await campaignsAPI.send(id);
|
||||
toast.success('Кампанията е изпратена');
|
||||
fetchCampaigns();
|
||||
} catch (error) {
|
||||
toast.error('Грешка при изпращане');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
if (confirm('Сигурни ли сте, че искате да изтриете тази кампания?')) {
|
||||
try {
|
||||
await campaignsAPI.delete(id);
|
||||
toast.success('Кампанията е изтрита');
|
||||
fetchCampaigns();
|
||||
} catch (error) {
|
||||
toast.error('Грешка при изтриване');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold">Имейл кампании</h1>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Нова кампания
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Нова имейл кампания</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Име на кампания *</Label>
|
||||
<Input
|
||||
required
|
||||
value={formData.campaignName}
|
||||
onChange={(e) => setFormData({ ...formData, campaignName: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Тема *</Label>
|
||||
<Input
|
||||
required
|
||||
value={formData.subject}
|
||||
onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Съдържание *</Label>
|
||||
<Textarea
|
||||
required
|
||||
rows={8}
|
||||
value={formData.body}
|
||||
onChange={(e) => setFormData({ ...formData, body: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Създай кампания
|
||||
</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Всички кампании</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="text-center py-8">Зареждане...</div>
|
||||
) : campaigns.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">Няма кампании</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Име</TableHead>
|
||||
<TableHead>Тема</TableHead>
|
||||
<TableHead>Статус</TableHead>
|
||||
<TableHead>Създадена</TableHead>
|
||||
<TableHead>Действия</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{campaigns.map((campaign) => (
|
||||
<TableRow key={campaign.CampaignID}>
|
||||
<TableCell className="font-medium">{campaign.CampaignName}</TableCell>
|
||||
<TableCell>{campaign.Subject}</TableCell>
|
||||
<TableCell>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||||
campaign.Status === 'Sent' ? 'bg-green-100 text-green-800' :
|
||||
campaign.Status === 'Draft' ? 'bg-gray-100 text-gray-800' :
|
||||
'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{campaign.Status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>{new Date(campaign.CreatedDate).toLocaleDateString('bg-BG')}</TableCell>
|
||||
<TableCell className="space-x-2">
|
||||
{campaign.Status === 'Draft' && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleSend(campaign.CampaignID)}
|
||||
>
|
||||
<Send className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(campaign.CampaignID)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user