2026-06-10 17:40:58 +05:30

808 lines
56 KiB
JavaScript

import React, { useState } from 'react';
import { Head, router, Link } from '@inertiajs/react';
import AdminLayout from '../../../Layouts/AdminLayout';
import {
Heart, Plus, Trash2, Send, CheckCircle2, Check, X,
Calendar, MapPin, Clock, Gift, Sparkles, Search,
ChevronDown, ChevronUp, Building, User, XCircle, Eye, BellRing
} from 'lucide-react';
const hoursList = [
'12:00', '12:30', '01:00', '01:30', '02:00', '02:30',
'03:00', '03:30', '04:00', '04:30', '05:00', '05:30',
'06:00', '06:30', '07:00', '07:30', '08:00', '08:30',
'09:00', '09:30', '10:00', '10:30', '11:00', '11:30'
];
export default function Announcements({ initialAnnouncements }) {
const [announcements, setAnnouncements] = useState(initialAnnouncements || []);
const [isFormOpen, setIsFormOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('All');
const [expandedCards, setExpandedCards] = useState({});
// Time picker states
const [startTimeHour, setStartTimeHour] = useState('09:00');
const [startTimeAmpm, setStartTimeAmpm] = useState('AM');
const [endTimeHour, setEndTimeHour] = useState('04:00');
const [endTimeAmpm, setEndTimeAmpm] = useState('PM');
const [isStartPickerOpen, setIsStartPickerOpen] = useState(false);
const [isEndPickerOpen, setIsEndPickerOpen] = useState(false);
const [newAnnouncement, setNewAnnouncement] = useState({
title: '',
content: '',
provided_items: '',
event_date: '',
event_time: '09:00 AM - 04:00 PM',
location_details: '',
location_pin: ''
});
const [errors, setErrors] = useState({});
const [toastMessage, setToastMessage] = useState(null);
const [rejectingAnnouncementId, setRejectingAnnouncementId] = useState(null);
const [rejectionRemarks, setRejectionRemarks] = useState('');
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 = 'Description is required';
if (!newAnnouncement.provided_items.trim()) newErrors.provided_items = 'Provided items is required';
if (!newAnnouncement.event_date.trim()) newErrors.event_date = 'Event date is required';
if (!newAnnouncement.event_time.trim()) newErrors.event_time = 'Event time is required';
if (!newAnnouncement.location_details.trim()) newErrors.location_details = 'Location details is required';
if (!newAnnouncement.location_pin.trim()) {
newErrors.location_pin = 'Location pin URL is required';
} else if (!newAnnouncement.location_pin.startsWith('http://') && !newAnnouncement.location_pin.startsWith('https://')) {
newErrors.location_pin = 'Must be a valid URL';
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
router.post(route('admin.events.store'), {
title: newAnnouncement.title,
content: newAnnouncement.content,
provided_items: newAnnouncement.provided_items,
event_date: newAnnouncement.event_date,
event_time: newAnnouncement.event_time,
location_details: newAnnouncement.location_details,
location_pin: newAnnouncement.location_pin
}, {
onSuccess: () => {
setNewAnnouncement({
title: '',
content: '',
provided_items: '',
event_date: '',
event_time: '09:00 AM - 04:00 PM',
location_details: '',
location_pin: ''
});
setStartTimeHour('09:00');
setStartTimeAmpm('AM');
setEndTimeHour('04:00');
setEndTimeAmpm('PM');
setErrors({});
setIsFormOpen(false);
showToast('Charity Event created successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const updateEventTime = (sh, sa, eh, ea) => {
setNewAnnouncement(prev => ({
...prev,
event_time: `${sh} ${sa} - ${eh} ${ea}`
}));
};
const handleApprove = (id) => {
router.post(route('admin.events.approve', id), {}, {
onSuccess: () => {
showToast('Charity Event approved successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleReject = (id) => {
setRejectingAnnouncementId(id);
setRejectionRemarks('');
};
const handleRejectSubmit = (e) => {
e.preventDefault();
if (!rejectionRemarks.trim()) return;
router.post(route('admin.events.reject', rejectingAnnouncementId), {
remarks: rejectionRemarks
}, {
onSuccess: () => {
setRejectingAnnouncementId(null);
setRejectionRemarks('');
showToast('Charity Event rejected successfully');
setTimeout(() => window.location.reload(), 500);
}
});
};
const handleDelete = (id) => {
if (confirm('Are you sure you want to delete this charity event?')) {
router.delete(route('admin.events.delete', id), {
onSuccess: () => {
showToast('Charity Event deleted');
setTimeout(() => window.location.reload(), 500);
}
});
}
};
const toggleExpand = (id) => {
setExpandedCards(prev => ({
...prev,
[id]: !prev[id]
}));
};
// Calculate dynamic statistics
const totalDrives = announcements.length;
const pendingCount = announcements.filter(a => a.status === 'pending').length;
const approvedCount = announcements.filter(a => a.status === 'approved').length;
const rejectedCount = announcements.filter(a => a.status === 'rejected').length;
// Filter announcements
const filteredAnnouncements = announcements.filter(ann => {
const matchesSearch =
ann.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
ann.content.toLowerCase().includes(searchQuery.toLowerCase()) ||
(ann.posted_by && ann.posted_by.toLowerCase().includes(searchQuery.toLowerCase())) ||
(ann.organization && ann.organization.toLowerCase().includes(searchQuery.toLowerCase())) ||
(ann.charityDetails?.provided_items && ann.charityDetails.provided_items.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesStatus = statusFilter === 'All' || ann.status === statusFilter.toLowerCase();
return matchesSearch && matchesStatus;
});
return (
<AdminLayout title="Charity Events & Drives">
<Head title="Charity Events - 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-7xl mx-auto pb-12">
{/* Upper Header section */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-black text-slate-900 tracking-tight">Charity Events & Drives</h1>
<p className="text-sm text-slate-500 font-medium">Manage, approve, or launch community charity initiatives.</p>
</div>
<button
onClick={() => setIsFormOpen(true)}
className="bg-[#0F6E56] hover:bg-[#0b523f] text-white px-5 py-2.5 rounded-xl font-bold text-sm flex items-center justify-center space-x-2 transition-all shadow-sm cursor-pointer border-none self-start sm:self-auto hover:scale-[1.02] active:scale-[0.98]"
>
<Plus className="w-4 h-4" />
<span>New Charity Event</span>
</button>
</div>
{/* Dashboard Stats Overview (Compact) */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-lg bg-teal-50 flex items-center justify-center text-teal-600 shrink-0">
<Heart className="w-5 h-5 fill-teal-500 text-teal-500" />
</div>
<div>
<div className="text-xs text-slate-400 font-bold uppercase tracking-wider">Total Drives</div>
<div className="text-xl font-black text-slate-800">{totalDrives}</div>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-lg bg-amber-50 flex items-center justify-center text-amber-600 shrink-0">
<Clock className="w-5 h-5 text-amber-500" />
</div>
<div>
<div className="text-xs text-slate-400 font-bold uppercase tracking-wider">Pending</div>
<div className="text-xl font-black text-slate-800">{pendingCount}</div>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-lg bg-emerald-50 flex items-center justify-center text-emerald-600 shrink-0">
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
</div>
<div>
<div className="text-xs text-slate-400 font-bold uppercase tracking-wider">Approved</div>
<div className="text-xl font-black text-slate-800">{approvedCount}</div>
</div>
</div>
<div className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex items-center space-x-3.5">
<div className="w-10 h-10 rounded-lg bg-rose-50 flex items-center justify-center text-rose-600 shrink-0">
<XCircle className="w-5 h-5 text-rose-500" />
</div>
<div>
<div className="text-xs text-slate-400 font-bold uppercase tracking-wider">Rejected</div>
<div className="text-xl font-black text-slate-800">{rejectedCount}</div>
</div>
</div>
</div>
{/* Search & Filtering Control Bar */}
<div className="bg-white p-3 rounded-xl border border-slate-200 shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
{/* Search Field */}
<div className="relative flex-1 max-w-md">
<Search className="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search events by title, description, or sponsor..."
className="w-full pl-10 pr-4 py-2 text-sm bg-slate-50 border border-slate-200 rounded-lg outline-none focus:bg-white focus:border-[#0F6E56] focus:ring-2 focus:ring-[#0F6E56]/10 transition-all"
/>
{searchQuery && (
<button
type="button"
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 text-xs bg-slate-200 hover:bg-slate-300 w-4 h-4 rounded-full flex items-center justify-center border-none cursor-pointer"
>
<X className="w-2.5 h-2.5" />
</button>
)}
</div>
{/* Filter Tabs */}
<div className="flex items-center space-x-1.5 overflow-x-auto self-start md:self-auto">
{['All', 'Pending', 'Approved', 'Rejected'].map((tab) => (
<button
key={tab}
type="button"
onClick={() => setStatusFilter(tab)}
className={`px-4 py-1.5 rounded-lg text-xs font-bold transition-all cursor-pointer border border-solid border-transparent ${
statusFilter === tab
? 'bg-[#0F6E56] text-white shadow-sm'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
}`}
>
{tab}
</button>
))}
</div>
</div>
{/* Event Creation Modal */}
{isFormOpen && (
<div className="fixed inset-0 bg-black/60 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 max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4 pb-2 border-b border-slate-100">
<div className="flex items-center space-x-2 text-[#0F6E56] font-bold">
<Heart className="w-5 h-5 fill-[#0F6E56]" />
<h2>Compose Charity Event</h2>
</div>
<button
type="button"
onClick={() => { setIsFormOpen(false); setErrors({}); }}
className="p-1 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 border-none bg-transparent cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleCreate} className="space-y-4">
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">Event 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-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all`}
placeholder="e.g. Free Dental checkup by Emirates Charity"
/>
{errors.title && <p className="text-red-500 text-xs mt-1 font-semibold">{errors.title}</p>}
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">Event Date</label>
<input
type="date"
value={newAnnouncement.event_date}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, event_date: e.target.value })}
className={`w-full h-11 px-3.5 rounded-xl border ${errors.event_date ? 'border-red-500' : 'border-slate-300'} text-xs focus:ring-2 focus:ring-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all`}
/>
{errors.event_date && <p className="text-red-500 text-[10px] mt-1 font-semibold">{errors.event_date}</p>}
</div>
<div className="relative">
{isStartPickerOpen && (
<div className="fixed inset-0 z-[45] bg-transparent" onClick={() => setIsStartPickerOpen(false)} />
)}
<div className="relative z-50">
<label className="block text-xs font-bold text-slate-600 mb-1">Start Time</label>
<button
type="button"
onClick={() => {
setIsStartPickerOpen(!isStartPickerOpen);
setIsEndPickerOpen(false);
}}
className={`w-full h-11 flex items-center justify-between px-3.5 bg-white border ${
isStartPickerOpen ? 'border-blue-500 ring-2 ring-blue-500/10' : 'border-slate-300'
} rounded-xl hover:border-blue-500 transition-all cursor-pointer text-left`}
>
<div className="flex items-center space-x-2 min-w-0">
<Clock className="w-4 h-4 text-slate-400 shrink-0" />
<div className="flex flex-col justify-center min-w-0 leading-tight">
<div className="text-[8px] text-blue-500 font-bold uppercase tracking-wider">Start with</div>
<div className="text-slate-800 text-xs font-bold truncate">{startTimeHour} {startTimeAmpm}</div>
</div>
</div>
<ChevronDown className="w-3.5 h-3.5 text-slate-400 shrink-0" />
</button>
{isStartPickerOpen && (
<div className="absolute top-full mt-1.5 left-0 bg-white border border-slate-200 shadow-xl rounded-xl p-2 z-50 flex space-x-2.5 w-48 animate-in fade-in slide-in-from-top-2">
<div className="flex-1 max-h-40 overflow-y-auto pr-1 space-y-1 scrollbar-thin">
{hoursList.map(h => (
<button
key={h}
type="button"
onClick={() => {
setStartTimeHour(h);
setIsStartPickerOpen(false);
updateEventTime(h, startTimeAmpm, endTimeHour, endTimeAmpm);
}}
className={`w-full text-left px-2 py-1 rounded text-xs font-semibold transition-colors ${
startTimeHour === h
? 'bg-blue-50 text-blue-600'
: 'text-slate-600 hover:bg-slate-50'
}`}
>
{h}
</button>
))}
</div>
<div className="flex flex-col space-y-1 justify-center border-l border-slate-100 pl-2">
{['AM', 'PM'].map(ampm => (
<button
key={ampm}
type="button"
onClick={() => {
setStartTimeAmpm(ampm);
updateEventTime(startTimeHour, ampm, endTimeHour, endTimeAmpm);
}}
className={`px-2 py-1 rounded text-[10px] font-black transition-all border border-solid ${
startTimeAmpm === ampm
? 'bg-blue-600 border-blue-600 text-white shadow-sm'
: 'border-slate-200 text-slate-400 hover:bg-slate-50'
}`}
>
{ampm}
</button>
))}
</div>
</div>
)}
</div>
</div>
<div className="relative">
{isEndPickerOpen && (
<div className="fixed inset-0 z-[45] bg-transparent" onClick={() => setIsEndPickerOpen(false)} />
)}
<div className="relative z-50">
<label className="block text-xs font-bold text-slate-600 mb-1">End Time</label>
<button
type="button"
onClick={() => {
setIsEndPickerOpen(!isEndPickerOpen);
setIsStartPickerOpen(false);
}}
className={`w-full h-11 flex items-center justify-between px-3.5 bg-white border ${
isEndPickerOpen ? 'border-blue-500 ring-2 ring-blue-500/10' : 'border-slate-300'
} rounded-xl hover:border-blue-500 transition-all cursor-pointer text-left`}
>
<div className="flex items-center space-x-2 min-w-0">
<Clock className="w-4 h-4 text-slate-400 shrink-0" />
<div className="flex flex-col justify-center min-w-0 leading-tight">
<div className="text-[8px] text-slate-500 font-bold uppercase tracking-wider">End with</div>
<div className="text-slate-800 text-xs font-bold truncate">{endTimeHour} {endTimeAmpm}</div>
</div>
</div>
<ChevronDown className="w-3.5 h-3.5 text-slate-400 shrink-0" />
</button>
{isEndPickerOpen && (
<div className="absolute top-full mt-1.5 right-0 bg-white border border-slate-200 shadow-xl rounded-xl p-2 z-50 flex space-x-2.5 w-48 animate-in fade-in slide-in-from-top-2">
<div className="flex-1 max-h-40 overflow-y-auto pr-1 space-y-1 scrollbar-thin">
{hoursList.map(h => (
<button
key={h}
type="button"
onClick={() => {
setEndTimeHour(h);
setIsEndPickerOpen(false);
updateEventTime(startTimeHour, startTimeAmpm, h, endTimeAmpm);
}}
className={`w-full text-left px-2 py-1 rounded text-xs font-semibold transition-colors ${
endTimeHour === h
? 'bg-blue-50 text-blue-600'
: 'text-slate-600 hover:bg-slate-50'
}`}
>
{h}
</button>
))}
</div>
<div className="flex flex-col space-y-1 justify-center border-l border-slate-100 pl-2">
{['AM', 'PM'].map(ampm => (
<button
key={ampm}
type="button"
onClick={() => {
setEndTimeAmpm(ampm);
updateEventTime(startTimeHour, startTimeAmpm, endTimeHour, ampm);
}}
className={`px-2 py-1 rounded text-[10px] font-black transition-all border border-solid ${
endTimeAmpm === ampm
? 'bg-blue-600 border-blue-600 text-white shadow-sm'
: 'border-slate-200 text-slate-400 hover:bg-slate-50'
}`}
>
{ampm}
</button>
))}
</div>
</div>
)}
</div>
</div>
</div>
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">What is Being Provided</label>
<input
type="text"
value={newAnnouncement.provided_items}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, provided_items: e.target.value })}
className={`w-full px-4 py-2.5 rounded-xl border ${errors.provided_items ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all`}
placeholder="e.g. Free Medical Checks & Food Supplies"
/>
{errors.provided_items && <p className="text-red-500 text-xs mt-1 font-semibold">{errors.provided_items}</p>}
</div>
<div className="grid grid-cols-1 gap-3">
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">Location Details</label>
<input
type="text"
value={newAnnouncement.location_details}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, location_details: e.target.value })}
className={`w-full px-4 py-2.5 rounded-xl border ${errors.location_details ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all`}
placeholder="e.g. Al Quoz Community Center, Dubai"
/>
{errors.location_details && <p className="text-red-500 text-xs mt-1 font-semibold">{errors.location_details}</p>}
</div>
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">Location Pin URL</label>
<input
type="text"
value={newAnnouncement.location_pin}
onChange={(e) => setNewAnnouncement({ ...newAnnouncement, location_pin: e.target.value })}
className={`w-full px-4 py-2.5 rounded-xl border ${errors.location_pin ? 'border-red-500' : 'border-slate-300'} text-sm focus:ring-2 focus:ring-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all`}
placeholder="e.g. https://maps.app.goo.gl/xyz"
/>
{errors.location_pin && <p className="text-red-500 text-xs mt-1 font-semibold">{errors.location_pin}</p>}
</div>
</div>
<div>
<label className="block text-xs font-bold text-slate-600 mb-1">Description & Instructions</label>
<textarea
rows="3"
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-[#0F6E56]/20 focus:border-[#0F6E56] outline-none transition-all resize-none`}
placeholder="Type event details and instructions here..."
></textarea>
{errors.content && <p className="text-red-500 text-xs mt-1 font-semibold">{errors.content}</p>}
</div>
<div className="flex items-center justify-end space-x-3 pt-2 border-t border-slate-100">
<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>Publish Event</span>
</button>
</div>
</form>
</div>
</div>
)}
{/* Rejection remarks modal */}
{rejectingAnnouncementId && (
<div className="fixed inset-0 bg-slate-900/60 backdrop-blur-sm flex items-center justify-center p-4 z-50 animate-fade-in">
<div className="bg-white rounded-3xl max-w-md w-full p-6 space-y-4 shadow-2xl border border-slate-100 transform scale-100 transition-all font-sans">
<div className="flex items-center justify-between border-b border-slate-100 pb-3">
<h3 className="text-base font-black text-slate-900 uppercase tracking-tight flex items-center gap-2">
<XCircle className="w-5 h-5 text-rose-500" />
<span>Reject Charity Event</span>
</h3>
<button
onClick={() => setRejectingAnnouncementId(null)}
className="text-slate-400 hover:text-slate-600 bg-transparent border-none cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleRejectSubmit} className="space-y-4">
<div className="space-y-2">
<label className="text-[10px] font-black text-slate-500 uppercase tracking-widest ml-1">
Reason for Rejection / Remarks
</label>
<textarea
required
rows="4"
value={rejectionRemarks}
onChange={(e) => setRejectionRemarks(e.target.value)}
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-2xl text-xs font-bold focus:ring-2 focus:ring-rose-500/20 focus:border-rose-500 outline-none resize-none transition-all"
placeholder="Please provide a reason for rejecting this event..."
></textarea>
</div>
<div className="flex space-x-2 pt-2">
<button
type="button"
onClick={() => setRejectingAnnouncementId(null)}
className="flex-1 bg-slate-100 hover:bg-slate-200 text-slate-700 py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-colors cursor-pointer border-none"
>
Cancel
</button>
<button
type="submit"
disabled={!rejectionRemarks.trim()}
className="flex-1 bg-rose-600 hover:bg-rose-700 text-white py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-colors cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-rose-200"
>
Reject Event
</button>
</div>
</form>
</div>
</div>
)}
{/* Compact Grid of Events */}
{filteredAnnouncements.length === 0 ? (
<div className="text-center py-16 bg-white rounded-2xl border border-slate-200 shadow-sm text-slate-500">
<Heart className="w-12 h-12 text-slate-300 mx-auto mb-3 fill-slate-100" />
<p className="font-bold text-sm">No charity events match your criteria.</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredAnnouncements.map((ann) => {
const details = ann.charityDetails;
const isExpanded = !!expandedCards[ann.id];
// Color accents based on status
const statusColorClass =
ann.status === 'approved' ? 'border-l-emerald-500' :
ann.status === 'rejected' ? 'border-l-rose-500' :
'border-l-amber-500';
return (
<div
key={ann.id}
className="group bg-white rounded-2xl border border-slate-200/80 hover:border-slate-300 shadow-sm hover:shadow-md transition-all duration-300 flex flex-col justify-between overflow-hidden relative"
>
{/* Top colored accent line */}
<div className={`h-1.5 w-full ${
ann.status === 'approved' ? 'bg-gradient-to-r from-emerald-400 to-teal-500' :
ann.status === 'rejected' ? 'bg-gradient-to-r from-rose-400 to-red-500' :
'bg-gradient-to-r from-amber-400 to-orange-500'
}`} />
{/* Top Card Area */}
<div className="p-5 flex-1 flex flex-col justify-between space-y-4">
<div className="space-y-3">
{/* Header Title + Actions */}
<div className="flex items-start justify-between gap-3">
<div className="space-y-1.5 flex-1 min-w-0">
{/* Status Badge */}
<div className="flex items-center space-x-2">
<span className={`px-2 py-0.5 rounded-full text-[9px] font-black uppercase tracking-wider shrink-0 border border-solid ${
ann.status === 'approved' ? 'bg-emerald-50 text-emerald-700 border-emerald-100' :
ann.status === 'rejected' ? 'bg-rose-50 text-rose-700 border-rose-100' :
'bg-amber-50 text-amber-700 border-amber-100'
}`}>
{ann.status}
</span>
{ann.type === 'charity' && (
<span className="bg-teal-50 text-teal-700 border border-solid border-teal-100 px-2 py-0.5 rounded-full text-[9px] font-black uppercase tracking-wider flex items-center gap-0.5">
<Heart className="w-2 h-2 fill-teal-600 text-teal-600" />
<span>Charity</span>
</span>
)}
</div>
{/* Event Title */}
<h3 className="font-extrabold text-sm text-slate-800 leading-snug tracking-tight group-hover:text-[#0F6E56] transition-colors duration-200" title={ann.title}>
{ann.title}
</h3>
</div>
{/* Action Control Buttons */}
<div className="flex items-center space-x-1 shrink-0">
{ann.status === 'pending' && (
<div className="flex items-center space-x-0.5 bg-slate-50 p-0.5 rounded-lg border border-slate-200">
<button
type="button"
onClick={() => handleApprove(ann.id)}
className="p-1 text-emerald-600 hover:bg-emerald-50 rounded transition-colors cursor-pointer border-none bg-transparent"
title="Approve"
>
<Check className="w-3.5 h-3.5" />
</button>
<button
type="button"
onClick={() => handleReject(ann.id)}
className="p-1 text-rose-600 hover:bg-rose-50 rounded transition-colors cursor-pointer border-none bg-transparent"
title="Reject"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
)}
<button
type="button"
onClick={() => handleDelete(ann.id)}
className="p-1.5 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors cursor-pointer border-none bg-transparent"
title="Delete"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
{/* Posted Organization & Author */}
{ann.posted_by_email ? (
<Link
href={`/admin/employers?search=${encodeURIComponent(ann.posted_by_email)}`}
className="flex items-center text-[10px] text-slate-500 font-bold bg-slate-50/60 hover:bg-slate-100/80 hover:border-slate-300 py-1 px-2.5 rounded-lg border border-slate-100 transition-all w-fit cursor-pointer no-underline"
title="Click to view Employer Profile"
>
<Building className="w-3 h-3 mr-1 text-slate-400 shrink-0" />
<span className="truncate max-w-[120px] text-slate-700" title={ann.organization}>{ann.organization}</span>
<span className="mx-1.5 text-slate-300"></span>
<User className="w-3 h-3 mr-1 text-slate-400 shrink-0" />
<span className="truncate max-w-[100px] text-slate-600" title={ann.posted_by}>{ann.posted_by}</span>
</Link>
) : (
<div className="flex items-center text-[10px] text-slate-500 font-bold bg-slate-50/60 py-1 px-2.5 rounded-lg border border-slate-100 w-fit">
<Building className="w-3 h-3 mr-1 text-slate-400 shrink-0" />
<span className="truncate max-w-[120px] text-slate-700" title={ann.organization}>{ann.organization}</span>
<span className="mx-1.5 text-slate-300"></span>
<User className="w-3 h-3 mr-1 text-slate-400 shrink-0" />
<span className="truncate max-w-[100px] text-slate-600" title={ann.posted_by}>{ann.posted_by}</span>
</div>
)}
</div>
{/* Description Content */}
<div className="space-y-2 text-xs">
<p className={`text-slate-600 leading-relaxed font-medium ${isExpanded ? '' : 'line-clamp-3'}`}>
{ann.content}
</p>
{ann.content.length > 90 && (
<button
type="button"
onClick={() => toggleExpand(ann.id)}
className="text-[#0F6E56] hover:text-[#0b523f] hover:underline font-bold flex items-center space-x-0.5 border-none bg-transparent cursor-pointer p-0 text-[10px] transition-colors"
>
<span>{isExpanded ? 'Show Less' : 'Read Full Description'}</span>
{isExpanded ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
</button>
)}
{ann.status === 'rejected' && ann.remarks && (
<div className="bg-rose-50/80 border border-rose-100 rounded-xl p-2.5 text-rose-800 text-[10px] font-semibold mt-1.5 leading-relaxed">
<span className="font-extrabold text-rose-900 block mb-0.5">Rejection Remarks:</span>
{ann.remarks}
</div>
)}
</div>
{/* Metadata Details container (Soft boxes/pills stacked vertically) */}
{details && (
<div className="pt-3 border-t border-slate-100/80 space-y-2 text-[10px] font-bold">
{/* Provided Items */}
<div className="flex items-center space-x-2.5 bg-rose-50/40 border border-rose-100/50 p-2 rounded-xl">
<div className="w-7 h-7 rounded-lg bg-rose-50 flex items-center justify-center text-rose-500 shrink-0 border border-rose-100">
<Gift className="w-3.5 h-3.5 fill-rose-500" />
</div>
<div className="min-w-0 leading-tight">
<div className="text-[8px] text-rose-400 font-extrabold uppercase tracking-wider">ITEMS PROVIDED</div>
<div className="truncate text-slate-700 font-bold text-xs" title={details.provided_items}>
{details.provided_items}
</div>
</div>
</div>
{/* Date & Time */}
<div className="flex items-center space-x-2.5 bg-amber-50/40 border border-amber-100/50 p-2 rounded-xl">
<div className="w-7 h-7 rounded-lg bg-amber-50 flex items-center justify-center text-amber-500 shrink-0 border border-amber-100">
<Calendar className="w-3.5 h-3.5 text-amber-600" />
</div>
<div className="min-w-0 leading-tight">
<div className="text-[8px] text-amber-500 font-extrabold uppercase tracking-wider">EVENT SCHEDULE</div>
<div className="truncate text-slate-700 font-bold text-xs" title={`${details.event_date} ${details.event_time}`}>
{details.event_date} ({details.event_time})
</div>
</div>
</div>
{/* Location */}
<div className="flex items-center space-x-2.5 bg-blue-50/40 border border-blue-100/50 p-2 rounded-xl">
<div className="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center text-blue-500 shrink-0 border border-blue-100">
<MapPin className="w-3.5 h-3.5 text-blue-600" />
</div>
<div className="min-w-0 leading-tight">
<div className="text-[8px] text-blue-400 font-extrabold uppercase tracking-wider">LOCATION</div>
<div className="truncate text-slate-700 font-bold text-xs" title={details.location_details}>
{details.location_details}
</div>
</div>
</div>
</div>
)}
</div>
{/* Bottom Card Footer */}
<div className="px-5 py-3 bg-slate-50/80 rounded-b-2xl border-t border-slate-100 flex items-center justify-between text-[9px] text-slate-400 font-bold">
<span>Published: {ann.created_at}</span>
{details?.location_pin && (
<a
href={details.location_pin}
target="_blank"
rel="noreferrer"
className="text-[#0F6E56] hover:text-[#0b523f] hover:underline flex items-center space-x-0.5 bg-white border border-slate-200/80 hover:border-slate-300 py-1 px-2.5 rounded-lg shadow-sm font-extrabold transition-all hover:scale-[1.02] active:scale-[0.98]"
>
<span>View on Map</span>
<MapPin className="w-2.5 h-2.5 text-[#0F6E56]" />
</a>
)}
</div>
</div>
);
})}
</div>
)}
</div>
</AdminLayout>
);
}