2026-06-08 15:51:51 +05:30

234 lines
14 KiB
JavaScript

import React, { useState } from 'react';
import { Head, router } from '@inertiajs/react';
import AdminLayout from '../../../Layouts/AdminLayout';
import { Megaphone, Plus, Trash2, Send, CheckCircle2, Check, X } from 'lucide-react';
export default function Announcements({ initialAnnouncements }) {
const [announcements, setAnnouncements] = useState(initialAnnouncements || []);
const [isFormOpen, setIsFormOpen] = useState(false);
const [newAnnouncement, setNewAnnouncement] = useState({ title: '', content: '', audience: 'Both' });
const [errors, setErrors] = useState({});
const [toastMessage, setToastMessage] = useState(null);
const showToast = (message) => {
setToastMessage(message);
setTimeout(() => setToastMessage(null), 3000);
};
const handleCreate = (e) => {
e.preventDefault();
let newErrors = {};
if (!newAnnouncement.title.trim()) newErrors.title = 'Title is required';
if (!newAnnouncement.content.trim()) newErrors.content = 'Content is required';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
router.post(route('admin.announcements.store'), {
title: newAnnouncement.title,
content: newAnnouncement.content,
type: newAnnouncement.audience === 'Both' ? 'info' : (newAnnouncement.audience === 'Employers' ? 'success' : 'warning')
}, {
onSuccess: () => {
setNewAnnouncement({ title: '', content: '', audience: 'Both' });
setErrors({});
setIsFormOpen(false);
showToast('Announcement broadcasted successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleApprove = (id) => {
router.post(route('admin.announcements.approve', id), {}, {
onSuccess: () => {
showToast('Announcement approved successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleReject = (id) => {
router.post(route('admin.announcements.reject', id), {}, {
onSuccess: () => {
showToast('Announcement rejected successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleDelete = (id) => {
if (confirm('Are you sure you want to delete this announcement?')) {
router.delete(route('admin.announcements.delete', id), {
onSuccess: () => {
showToast('Announcement deleted');
setTimeout(() => window.location.reload(), 500);
}
});
}
};
return (
<AdminLayout title="Announcements">
<Head title="Announcements - Admin Portal" />
{/* Toast Notification */}
{toastMessage && (
<div className="fixed bottom-4 right-4 bg-slate-900 text-white px-6 py-3 rounded-xl shadow-2xl flex items-center space-x-3 z-50 animate-in slide-in-from-bottom-5">
<CheckCircle2 className="w-5 h-5 text-emerald-400" />
<span className="font-medium text-sm">{toastMessage}</span>
</div>
)}
<div className="space-y-6 select-none max-w-6xl mx-auto">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-900">Platform Announcements & Events</h1>
<p className="text-sm text-slate-500">Approve user-submitted events or broadcast messages to all users.</p>
</div>
<button
onClick={() => setIsFormOpen(true)}
className="bg-[#185FA5] hover:bg-[#144f8a] text-white px-5 py-2.5 rounded-xl font-bold text-sm flex items-center space-x-2 transition-colors shadow-sm cursor-pointer border-none"
>
<Plus className="w-4 h-4" />
<span>New Announcement</span>
</button>
</div>
{isFormOpen && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in">
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-2xl w-full max-w-lg animate-in slide-in-from-bottom-4">
<div className="flex items-center space-x-2 mb-4 text-[#185FA5] font-bold">
<Megaphone className="w-5 h-5" />
<h2>Compose Message</h2>
</div>
<form onSubmit={handleCreate} className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Title</label>
<input
type="text"
value={newAnnouncement.title}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, title: e.target.value })}
className={`w-full px-4 py-2.5 rounded-xl border ${errors.title ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all`}
placeholder="Enter announcement title..."
/>
{errors.title && <p className="text-red-500 text-xs mt-1">{errors.title}</p>}
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Content</label>
<textarea
rows="4"
value={newAnnouncement.content}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, content: e.target.value })}
className={`w-full px-4 py-3 rounded-xl border ${errors.content ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all resize-none`}
placeholder="Type your message here..."
></textarea>
{errors.content && <p className="text-red-500 text-xs mt-1">{errors.content}</p>}
</div>
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Target Audience</label>
<select
value={newAnnouncement.audience}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, audience: e.target.value })}
className="w-full px-4 py-2.5 rounded-xl border border-slate-300 text-sm focus:ring-2 focus:ring-[#185FA5]/20 focus:border-[#185FA5] outline-none transition-all bg-white"
>
<option value="Both">Both Employers & Workers</option>
<option value="Employers">Employers Only</option>
<option value="Workers">Workers Only</option>
</select>
</div>
<div className="flex items-center justify-end space-x-3 pt-2">
<button
type="button"
onClick={() => { setIsFormOpen(false); setErrors({}); }}
className="px-5 py-2.5 rounded-xl text-sm font-semibold text-slate-600 hover:bg-slate-100 transition-colors cursor-pointer border-none"
>
Cancel
</button>
<button
type="submit"
className="bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-2.5 rounded-xl text-sm font-bold flex items-center space-x-2 transition-colors shadow-sm cursor-pointer border-none"
>
<Send className="w-4 h-4" />
<span>Broadcast Message</span>
</button>
</div>
</form>
</div>
</div>
)}
<div className="space-y-4">
{announcements.length === 0 ? (
<div className="text-center py-16 bg-white rounded-2xl border border-slate-200 shadow-sm text-slate-500">
<Megaphone className="w-12 h-12 text-slate-300 mx-auto mb-3" />
<p className="font-medium text-sm">No announcements published yet.</p>
</div>
) : (
announcements.map((ann) => (
<div key={ann.id} className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm flex flex-col sm:flex-row sm:items-start justify-between gap-4 group">
<div className="space-y-2 flex-1">
<div className="flex items-center space-x-3 flex-wrap gap-y-2">
<h3 className="font-bold text-lg text-slate-900 tracking-tight">{ann.title}</h3>
<span className={`px-2.5 py-1 rounded-md text-[10px] font-bold uppercase tracking-wider ${
ann.type === 'info' ? 'bg-purple-100 text-purple-700' :
ann.type === 'success' ? 'bg-blue-100 text-blue-700' :
'bg-emerald-100 text-emerald-700'
}`}>
Type: {ann.type}
</span>
<span className={`px-2.5 py-1 rounded-md text-[10px] font-bold uppercase border ${
ann.status === 'approved' ? 'bg-emerald-50 text-emerald-700 border-emerald-200' :
ann.status === 'rejected' ? 'bg-rose-50 text-rose-700 border-rose-200' :
'bg-yellow-50 text-yellow-700 border-yellow-200'
}`}>
Status: {ann.status}
</span>
</div>
<p className="text-sm text-slate-600 leading-relaxed whitespace-pre-wrap">{ann.content}</p>
<div className="flex items-center space-x-2 text-[11px] font-medium text-slate-400">
<span>Posted by: <strong className="text-slate-600">{ann.posted_by}</strong> ({ann.organization})</span>
<span></span>
<span>Published: {ann.created_at}</span>
</div>
</div>
<div className="flex items-center space-x-2 self-start flex-wrap gap-2">
{ann.status === 'pending' && (
<>
<button
onClick={() => handleApprove(ann.id)}
className="px-3 py-1.5 bg-emerald-50 hover:bg-emerald-100 text-emerald-700 rounded-xl transition-colors cursor-pointer flex items-center space-x-1 font-bold text-xs border border-solid border-emerald-200"
title="Approve Announcement"
>
<Check className="w-3.5 h-3.5" />
<span>Approve</span>
</button>
<button
onClick={() => handleReject(ann.id)}
className="px-3 py-1.5 bg-rose-50 hover:bg-rose-100 text-rose-700 rounded-xl transition-colors cursor-pointer flex items-center space-x-1 font-bold text-xs border border-solid border-rose-200"
title="Reject Announcement"
>
<X className="w-3.5 h-3.5" />
<span>Reject</span>
</button>
</>
)}
<button
onClick={() => handleDelete(ann.id)}
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-xl transition-colors cursor-pointer border-none bg-transparent"
title="Delete Announcement"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
</div>
))
)}
</div>
</div>
</AdminLayout>
);
}