From 4deb09d9305b2dd821e92fc117123d03d2be208a Mon Sep 17 00:00:00 2001 From: Abiram070207 Date: Tue, 23 Jun 2026 10:31:18 +0530 Subject: [PATCH] feat: keep manage venue as separate column under main menu in admin panel --- frontend/src/App.tsx | 3 + frontend/src/components/DashboardLayout.tsx | 3 +- frontend/src/components/Sidebar.tsx | 4 +- .../components/dashboard/ClassManagement.tsx | 236 --------------- .../components/dashboard/VenueManagement.tsx | 271 ++++++++++++++++++ 5 files changed, 279 insertions(+), 238 deletions(-) create mode 100644 frontend/src/components/dashboard/VenueManagement.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dd03858..e0cef03 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,6 +25,7 @@ import { StudentDashboard } from './components/dashboard/StudentDashboard'; import { StudentRegistrationsView } from './components/dashboard/StudentRegistrationsView'; import { ManageNoticesView } from './components/dashboard/ManageNoticesView'; import { ManageStudentsView } from './components/dashboard/ManageStudentsView'; +import { VenueManagement } from './components/dashboard/VenueManagement'; const AppContent: React.FC = () => { @@ -156,6 +157,8 @@ const AppContent: React.FC = () => { return ; case 'classes': return user?.role === 'ADMIN' ? : isDashboard; + case 'manage-venues': + return user?.role === 'ADMIN' ? : isDashboard; case 'automation': return (user?.role === 'ADMIN' || user?.role === 'PRINCIPAL' || user?.role === 'HOD') ? : isDashboard; case 'import-excel': diff --git a/frontend/src/components/DashboardLayout.tsx b/frontend/src/components/DashboardLayout.tsx index bae41d3..c9a2671 100644 --- a/frontend/src/components/DashboardLayout.tsx +++ b/frontend/src/components/DashboardLayout.tsx @@ -1,5 +1,5 @@ import { Sidebar } from './Sidebar'; -import { Search, Bell, Settings, LogOut, User as UserIcon, Plus, Calendar, ClipboardList, CheckCircle, Users, BookOpen, Zap, Clock, History as HistoryIcon, LayoutGrid, FileSpreadsheet } from 'lucide-react'; +import { Search, Bell, Settings, LogOut, User as UserIcon, Plus, Calendar, ClipboardList, CheckCircle, Users, BookOpen, Zap, Clock, History as HistoryIcon, LayoutGrid, FileSpreadsheet, Building2 } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import { useState, useEffect, useRef } from 'react'; import { API_BASE_URL } from '../lib/config'; @@ -50,6 +50,7 @@ export const DashboardLayout: React.FC = ({ { id: 'history', label: 'History', icon: HistoryIcon, description: 'Event ledger and audit trail' }, { id: 'user-management', label: 'Manage Users', icon: Users, description: 'User roles and access', roles: ['ADMIN'] }, { id: 'classes', label: 'Classes', icon: BookOpen, description: 'Academic structure', roles: ['ADMIN'] }, + { id: 'manage-venues', label: 'Manage Venues', icon: Building2, description: 'Configure college venues and capacities', roles: ['ADMIN'] }, { id: 'import-excel', label: 'Import Excel', icon: FileSpreadsheet, description: 'Bulk upload from response sheets', roles: ['ADMIN', 'PRINCIPAL'] }, { id: 'automation', label: 'Automation', icon: Zap, description: 'Smart scheduling tools', roles: ['ADMIN', 'PRINCIPAL', 'HOD'] }, ].filter(item => !item.roles || item.roles.includes(user?.role || '')); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 94e31f4..ec19d62 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -14,7 +14,8 @@ import { HelpCircle, ExternalLink, CheckCircle, - Bell + Bell, + Building2 } from 'lucide-react'; import { cn } from '../lib/utils'; import { motion } from 'framer-motion'; @@ -39,6 +40,7 @@ export const Sidebar: React.FC = ({ activeItem, onItemClick }) => { icon: HistoryIcon, label: 'History', id: 'history' }, ...((user?.role === 'ADMIN' || user?.role === 'PRINCIPAL' || user?.role === 'HOD') ? [{ icon: Zap, label: 'Automation', id: 'automation' }] : []), ...(user?.role === 'ADMIN' ? [{ icon: BookOpen, label: 'Classes', id: 'classes' }] : []), + ...(user?.role === 'ADMIN' ? [{ icon: Building2, label: 'Manage Venues', id: 'manage-venues' }] : []), ...(user?.role === 'ADMIN' ? [{ icon: Users, label: 'Manage Users', id: 'user-management' }] : []), ...(user?.role === 'ADMIN' ? [{ icon: Bell, label: 'Manage Notices', id: 'manage-notices' }] : []), ...((user?.role === 'FACULTY' || user?.role === 'HOD' || user?.role === 'ADMIN') ? [{ icon: Users, label: 'Student Hub', id: 'student-registrations' }] : []), diff --git a/frontend/src/components/dashboard/ClassManagement.tsx b/frontend/src/components/dashboard/ClassManagement.tsx index 21f4115..7a216a2 100644 --- a/frontend/src/components/dashboard/ClassManagement.tsx +++ b/frontend/src/components/dashboard/ClassManagement.tsx @@ -57,14 +57,6 @@ export const ClassManagement: React.FC = () => { const [selectedBatchDept, setSelectedBatchDept] = useState('AI&DS'); const [tempBatches, setTempBatches] = useState([]); - // Venue Management State - const [venues, setVenues] = useState([]); - const [newVenueName, setNewVenueName] = useState(''); - const [newVenueCapacity, setNewVenueCapacity] = useState(''); - const [editingVenueId, setEditingVenueId] = useState(null); - const [editVenueName, setEditVenueName] = useState(''); - const [editVenueCapacity, setEditVenueCapacity] = useState(''); - useEffect(() => { const deptBatches = batches.filter(b => b.department === selectedBatchDept); setTempBatches(JSON.parse(JSON.stringify(deptBatches))); @@ -81,108 +73,8 @@ export const ClassManagement: React.FC = () => { useEffect(() => { fetchClasses(); fetchBatches(); - fetchVenues(); }, []); - const fetchVenues = async () => { - try { - const response = await fetch(API_BASE_URL + '/api/venues'); - if (response.ok) { - const data = await response.json(); - setVenues(data); - } - } catch (error) { - console.error('Failed to fetch venues:', error); - } - }; - - const handleCreateVenue = async (e: React.FormEvent) => { - e.preventDefault(); - if (!newVenueName.trim()) { - showAlert('Required', 'Please enter a venue name', 'error'); - return; - } - - try { - const capacityVal = newVenueCapacity.trim() ? Number(newVenueCapacity) : null; - const response = await fetch(API_BASE_URL + '/api/venues', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - name: newVenueName.trim(), - capacity: capacityVal - }) - }); - - if (response.ok) { - fetchVenues(); - setNewVenueName(''); - setNewVenueCapacity(''); - showAlert('Success', 'Venue added successfully.', 'success'); - } - } catch (error) { - console.error('Failed to add venue:', error); - showAlert('Error', 'Failed to add venue.', 'error'); - } - }; - - const handleStartEditVenue = (venue: any) => { - setEditingVenueId(venue.id); - setEditVenueName(venue.name); - setEditVenueCapacity(venue.capacity !== null && venue.capacity !== undefined ? String(venue.capacity) : ''); - }; - - const handleSaveVenueEdit = async (venueId: string) => { - if (!editVenueName.trim()) { - showAlert('Required', 'Venue name cannot be empty', 'error'); - return; - } - - try { - const capacityVal = editVenueCapacity.trim() ? Number(editVenueCapacity) : null; - const response = await fetch(API_BASE_URL + '/api/venues', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - id: venueId, - name: editVenueName.trim(), - capacity: capacityVal - }) - }); - - if (response.ok) { - fetchVenues(); - setEditingVenueId(null); - showAlert('Success', 'Venue updated successfully.', 'success'); - } - } catch (error) { - console.error('Failed to update venue:', error); - showAlert('Error', 'Failed to update venue.', 'error'); - } - }; - - const handleDeleteVenue = async (venueId: string) => { - showConfirm( - 'Delete Venue', - 'Are you sure you want to delete this venue? This cannot be undone.', - async () => { - try { - const response = await fetch(`${API_BASE_URL}/api/venues/${venueId}`, { - method: 'DELETE' - }); - - if (response.ok) { - fetchVenues(); - showAlert('Success', 'Venue deleted successfully.', 'success'); - } - } catch (error) { - console.error('Failed to delete venue:', error); - showAlert('Error', 'Failed to delete venue.', 'error'); - } - } - ); - }; - const fetchBatches = async () => { try { const response = await fetch(API_BASE_URL + '/api/batches'); @@ -748,134 +640,6 @@ export const ClassManagement: React.FC = () => { )} - - {/* Manage Venues Section */} -
-
-
-
- -
-
-

Manage Venues

-

Configure college venues, halls, and seating capacities.

-
-
- -
-
- setNewVenueName(e.target.value)} - className="bg-slate-50 border border-slate-200 rounded-xl py-2 px-4 text-xs font-bold focus:bg-white focus:border-brand-indigo outline-none transition-all text-slate-800" - required - /> - setNewVenueCapacity(e.target.value)} - className="bg-slate-50 border border-slate-200 rounded-xl py-2 px-4 text-xs font-bold focus:bg-white focus:border-brand-indigo outline-none transition-all text-slate-800 w-32" - /> - -
-
-
- -
- {venues.map(venue => ( -
- {editingVenueId === venue.id ? ( -
-
- - setEditVenueName(e.target.value)} - className="w-full bg-white border border-slate-200 rounded-xl py-1.5 px-3 text-xs font-bold focus:border-brand-indigo outline-none transition-all" - required - /> -
-
- - setEditVenueCapacity(e.target.value)} - className="w-full bg-white border border-slate-200 rounded-xl py-1.5 px-3 text-xs font-bold focus:border-brand-indigo outline-none transition-all" - placeholder="Unlimited / Custom" - /> -
-
- - -
-
- ) : ( - <> -
-
-
- -
-
-

- {venue.name} -

- - {venue.capacity ? `Capacity: ${venue.capacity} seats` : 'No Limit / Custom'} - -
-
-
- -
- - -
- - )} -
- ))} - {venues.length === 0 && ( -
- No venues registered. Use the form above to add a new venue. -
- )} -
-
); }; diff --git a/frontend/src/components/dashboard/VenueManagement.tsx b/frontend/src/components/dashboard/VenueManagement.tsx new file mode 100644 index 0000000..1c5c455 --- /dev/null +++ b/frontend/src/components/dashboard/VenueManagement.tsx @@ -0,0 +1,271 @@ +import { API_BASE_URL } from '../../lib/config'; +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { + Plus, + Trash2, + Building2, + Edit, + Save, + CheckCircle2 +} from 'lucide-react'; +import { useDialog } from '../../context/DialogContext'; + +export const VenueManagement: React.FC = () => { + const { showAlert, showConfirm } = useDialog(); + const [venues, setVenues] = useState([]); + const [newVenueName, setNewVenueName] = useState(''); + const [newVenueCapacity, setNewVenueCapacity] = useState(''); + const [editingVenueId, setEditingVenueId] = useState(null); + const [editVenueName, setEditVenueName] = useState(''); + const [editVenueCapacity, setEditVenueCapacity] = useState(''); + + useEffect(() => { + fetchVenues(); + }, []); + + const fetchVenues = async () => { + try { + const response = await fetch(API_BASE_URL + '/api/venues'); + if (response.ok) { + const data = await response.json(); + setVenues(data); + } + } catch (error) { + console.error('Failed to fetch venues:', error); + } + }; + + const handleCreateVenue = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newVenueName.trim()) { + showAlert('Required', 'Please enter a venue name', 'error'); + return; + } + + try { + const capacityVal = newVenueCapacity.trim() ? Number(newVenueCapacity) : null; + const response = await fetch(API_BASE_URL + '/api/venues', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: newVenueName.trim(), + capacity: capacityVal + }) + }); + + if (response.ok) { + fetchVenues(); + setNewVenueName(''); + setNewVenueCapacity(''); + showAlert('Success', 'Venue added successfully.', 'success'); + } + } catch (error) { + console.error('Failed to add venue:', error); + showAlert('Error', 'Failed to add venue.', 'error'); + } + }; + + const handleStartEditVenue = (venue: any) => { + setEditingVenueId(venue.id); + setEditVenueName(venue.name); + setEditVenueCapacity(venue.capacity !== null && venue.capacity !== undefined ? String(venue.capacity) : ''); + }; + + const handleSaveVenueEdit = async (venueId: string) => { + if (!editVenueName.trim()) { + showAlert('Required', 'Venue name cannot be empty', 'error'); + return; + } + + try { + const capacityVal = editVenueCapacity.trim() ? Number(editVenueCapacity) : null; + const response = await fetch(API_BASE_URL + '/api/venues', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: venueId, + name: editVenueName.trim(), + capacity: capacityVal + }) + }); + + if (response.ok) { + fetchVenues(); + setEditingVenueId(null); + showAlert('Success', 'Venue updated successfully.', 'success'); + } + } catch (error) { + console.error('Failed to update venue:', error); + showAlert('Error', 'Failed to update venue.', 'error'); + } + }; + + const handleDeleteVenue = async (venueId: string) => { + showConfirm( + 'Delete Venue', + 'Are you sure you want to delete this venue? This cannot be undone.', + async () => { + try { + const response = await fetch(`${API_BASE_URL}/api/venues/${venueId}`, { + method: 'DELETE' + }); + + if (response.ok) { + fetchVenues(); + showAlert('Success', 'Venue deleted successfully.', 'success'); + } + } catch (error) { + console.error('Failed to delete venue:', error); + showAlert('Error', 'Failed to delete venue.', 'error'); + } + } + ); + }; + + return ( +
+ {/* Header */} +
+
+

Venue Management

+

Configure college venues, halls, and seating capacities

+
+ +
+
+ +
+
+
+ + {/* Manage Venues Section */} +
+
+
+
+ +
+
+

Registered Venues

+

Configure college venues, halls, and seating capacities.

+
+
+ +
+
+ setNewVenueName(e.target.value)} + className="bg-slate-50 border border-slate-200 rounded-xl py-2 px-4 text-xs font-bold focus:bg-white focus:border-brand-indigo outline-none transition-all text-slate-800" + required + /> + setNewVenueCapacity(e.target.value)} + className="bg-slate-50 border border-slate-200 rounded-xl py-2 px-4 text-xs font-bold focus:bg-white focus:border-brand-indigo outline-none transition-all text-slate-800 w-32" + /> + +
+
+
+ +
+ {venues.map(venue => ( +
+ {editingVenueId === venue.id ? ( +
+
+ + setEditVenueName(e.target.value)} + className="w-full bg-white border border-slate-200 rounded-xl py-1.5 px-3 text-xs font-bold focus:border-brand-indigo outline-none transition-all" + required + /> +
+
+ + setEditVenueCapacity(e.target.value)} + className="w-full bg-white border border-slate-200 rounded-xl py-1.5 px-3 text-xs font-bold focus:border-brand-indigo outline-none transition-all" + placeholder="Unlimited / Custom" + /> +
+
+ + +
+
+ ) : ( + <> +
+
+
+ +
+
+

+ {venue.name} +

+ + {venue.capacity ? `Capacity: ${venue.capacity} seats` : 'No Limit / Custom'} + +
+
+
+ +
+ + +
+ + )} +
+ ))} + {venues.length === 0 && ( +
+ No venues registered. Use the form above to add a new venue. +
+ )} +
+
+
+ ); +};