182 lines
8.7 KiB
TypeScript
182 lines
8.7 KiB
TypeScript
import React, { useState, useEffect, useMemo } from 'react';
|
|
import type { Event, Announcement, SpecialEvent } from '../../types';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import SpecialEventsBanner from './SpecialEventsBanner';
|
|
import AccreditationsSection from './AccreditationsSection';
|
|
|
|
interface HomeDashboardProps {
|
|
events: Event[];
|
|
announcements: Announcement[];
|
|
onNavigateToEvents: () => void;
|
|
specialEvents: SpecialEvent[];
|
|
settings?: Record<string, any>;
|
|
}
|
|
|
|
const HomeDashboard: React.FC<HomeDashboardProps> = ({
|
|
events = [],
|
|
announcements = [],
|
|
onNavigateToEvents,
|
|
specialEvents = [],
|
|
settings = {}
|
|
}) => {
|
|
const [now, setNow] = useState(new Date());
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => setNow(new Date()), 30000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
const filteredAnnouncements = useMemo(() => {
|
|
return (announcements || []).filter(ann => {
|
|
if (!ann.expiresAt) return true;
|
|
const expiry = new Date(ann.expiresAt);
|
|
return expiry > now;
|
|
});
|
|
}, [announcements, now]);
|
|
|
|
// Generate stable random values for rotation and color based on announcement ID
|
|
const getNoteStyle = (id: string) => {
|
|
// Simple hash function for stability
|
|
const hash = id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
const rotations = [-2, -1, 1, 2, 3, -3];
|
|
const colors = [
|
|
'bg-[#fdfd96]', // Classic Yellow
|
|
'bg-[#ff7eb9]', // Hot Pink
|
|
'bg-[#7afcff]', // Light Blue
|
|
'bg-[#feff9c]', // Light Yellow
|
|
'bg-[#fff740]' // Darker Yellow
|
|
];
|
|
|
|
return {
|
|
rotation: rotations[hash % rotations.length],
|
|
color: colors[hash % colors.length]
|
|
};
|
|
};
|
|
|
|
return (
|
|
<div className="px-6 md:px-12 lg:px-24 py-12 bg-transparent animate-in fade-in duration-700">
|
|
<div
|
|
onClick={onNavigateToEvents}
|
|
className="relative w-full h-64 md:h-80 rounded-[2.5rem] overflow-hidden cursor-pointer group mb-12 shadow-xl shadow-gray-200/20 hover:shadow-2xl transition-all duration-500"
|
|
>
|
|
<div className="absolute inset-0 bg-black/40 group-hover:bg-black/30 transition-colors duration-500 z-10"></div>
|
|
<img
|
|
src="https://img.freepik.com/premium-photo/outdoor-music-festival-evening-ambiance-with-festive-lights-crowd_114541-6739.jpg"
|
|
alt="Events"
|
|
className="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
<div className="absolute inset-0 z-20 flex flex-col justify-center px-12 md:px-20">
|
|
<div className="transform transition-transform duration-500 group-hover:translate-x-2 space-y-4">
|
|
<h2 className="text-4xl md:text-6xl font-serif text-white font-bold tracking-tight drop-shadow-lg">
|
|
Let's get started
|
|
</h2>
|
|
<div className="flex items-center gap-3 text-white/90 group-hover:text-[#f97316] transition-colors w-fit">
|
|
<span className="text-sm font-bold uppercase tracking-widest bg-black/20 backdrop-blur-sm px-4 py-2 rounded-full border border-white/20 group-hover:bg-white group-hover:text-[#f97316] transition-all duration-300">
|
|
Explore Events <i className="fas fa-arrow-right ml-2"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Notice Board Section */}
|
|
<div className="w-full bg-[#e8e4c9] p-8 md:p-12 rounded-[2rem] shadow-2xl border-[12px] border-[#8d6e63] relative overflow-hidden min-h-[600px] mt-12">
|
|
{/* Cork texture pattern */}
|
|
<div className="absolute inset-0 opacity-30 bg-[url('https://www.transparenttextures.com/patterns/cork-board.png')] pointer-events-none"></div>
|
|
|
|
<div className="relative z-10 mb-12 flex flex-col md:flex-row items-start md:items-center justify-between gap-6 border-b-2 border-[#8d6e63]/20 pb-6">
|
|
<div>
|
|
<h2 className="text-4xl md:text-5xl font-serif text-[#3e2723] tracking-tight font-bold drop-shadow-sm mb-2">
|
|
Campus Notice Board
|
|
</h2>
|
|
<p className="text-[#5d4037] font-medium opacity-80">Real-time updates from the administration</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-12 p-6">
|
|
<AnimatePresence mode="popLayout">
|
|
{filteredAnnouncements.length > 0 ? (
|
|
filteredAnnouncements.slice(0, 3).map((ann) => {
|
|
const style = getNoteStyle(ann.id);
|
|
return (
|
|
<motion.div
|
|
key={ann.id}
|
|
layout
|
|
initial={{ scale: 0.8, opacity: 0, y: 50, rotate: style.rotation + (Math.random() * 10 - 5) }}
|
|
animate={{ scale: 1, opacity: 1, y: 0, rotate: style.rotation }}
|
|
exit={{ scale: 0.8, opacity: 0, transition: { duration: 0.2 } }}
|
|
whileHover={{ scale: 1.05, rotate: 0, zIndex: 50, transition: { duration: 0.2 } }}
|
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
className={`${style.color} p-8 shadow-[4px_4px_10px_rgba(0,0,0,0.2)] hover:shadow-[15px_15px_30px_rgba(0,0,0,0.3)] transition-shadow duration-300 relative group cursor-pointer min-h-[300px] flex flex-col transform-gpu`}
|
|
>
|
|
{/* Pin */}
|
|
<div className="absolute -top-3 left-1/2 -translate-x-1/2 w-5 h-5 rounded-full bg-[#d32f2f] shadow-[2px_2px_4px_rgba(0,0,0,0.3)] z-20 border border-[#b71c1c] after:content-[''] after:absolute after:top-1 after:left-1 after:w-1.5 after:h-1.5 after:bg-white/50 after:rounded-full"></div>
|
|
|
|
<div className="mb-6 pt-2">
|
|
<span className={`text-xs font-black uppercase tracking-widest px-3 py-1.5 rounded-sm ${
|
|
ann.type === 'URGENT' ? 'bg-red-500/20 text-red-800' :
|
|
ann.type === 'DELAY' ? 'bg-amber-500/20 text-amber-800' :
|
|
'bg-black/5 text-gray-800'
|
|
}`}>
|
|
{ann.type || 'NOTICE'}
|
|
</span>
|
|
</div>
|
|
|
|
<h3 className="font-serif font-bold text-2xl text-gray-900 mb-4 leading-tight">
|
|
{ann.title}
|
|
</h3>
|
|
|
|
{(ann as any).image && (
|
|
<img
|
|
src={(ann as any).image}
|
|
alt={ann.title}
|
|
className="w-full h-40 object-cover rounded-xl mb-4 shrink-0 shadow-sm border border-black/10"
|
|
/>
|
|
)}
|
|
|
|
<p className="font-sans text-gray-800 text-base flex-grow mb-6 leading-relaxed opacity-90">
|
|
{ann.message}
|
|
</p>
|
|
|
|
<div className="mt-auto pt-4 border-t border-black/10 flex flex-col gap-2 text-[11px] font-bold text-gray-700 uppercase tracking-wider">
|
|
<div className="flex justify-between items-center">
|
|
<span className="flex items-center gap-2">
|
|
<i className="far fa-calendar-alt"></i>
|
|
{new Date(ann.timestamp).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })}
|
|
</span>
|
|
<span className="flex items-center gap-2">
|
|
<i className="far fa-clock"></i>
|
|
{new Date(ann.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
|
|
</span>
|
|
</div>
|
|
{ann.expiresAt && <span className="text-red-700/70 text-right">Exp: {new Date(ann.expiresAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>}
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
})
|
|
) : (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="col-span-full flex flex-col items-center justify-center py-32 text-[#8d6e63]/50"
|
|
>
|
|
<i className="fas fa-thumbtack text-6xl mb-6 opacity-30 rotate-45"></i>
|
|
<p className="font-serif text-2xl font-bold">The board is empty right now.</p>
|
|
<p className="font-sans text-sm mt-2 uppercase tracking-widest">Check back later for updates</p>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
|
|
<AccreditationsSection />
|
|
|
|
<SpecialEventsBanner specialEvents={specialEvents} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HomeDashboard;
|