Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/maps/AB3-First-Floor.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/app/dashboard/faculty/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { useRouter } from 'next/navigation';
import { facultyDashboardService } from '@/services/facultyDashboardService';
import toast, { Toaster } from 'react-hot-toast';

// --- NEW IMPORT ---
import StatusMapManager from '@/components/dashboard/StatusMapManager';

const getUserId = () => {
if (typeof window !== "undefined") {
const userStr = localStorage.getItem("user");
Expand Down Expand Up @@ -196,6 +199,8 @@ export default function FacultyProfilePage() {

<div className="p-6 space-y-6">



{/* Profile Picture */}
<div className="flex flex-col items-center -mt-2">
<div className="relative">
Expand Down Expand Up @@ -231,6 +236,10 @@ export default function FacultyProfilePage() {
placeholder="Dr. Your Name"
/>
</div>
{/* --- NEW: LIVE STATUS & MAP MANAGER --- */}
{/* Only show if we have a valid User ID */}
{userId && <StatusMapManager facultyId={userId} />}
{/* -------------------------------------- */}

{/* Basic Info Fields */}
<div className="space-y-4">
Expand Down
354 changes: 237 additions & 117 deletions src/app/dashboard/student/all-faculty/page.tsx

Large diffs are not rendered by default.

256 changes: 196 additions & 60 deletions src/app/dashboard/student/faculty/[id]/page.tsx

Large diffs are not rendered by default.

155 changes: 86 additions & 69 deletions src/app/dashboard/student/faculty/page.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,50 @@
"use client";

import React, { useState } from 'react';
import { Search, MapPin, GraduationCap, ChevronLeft, ChevronRight, Filter } from 'lucide-react';
import Link from 'next/link';
import React, { useState, useEffect } from 'react';
import { Search, MapPin, GraduationCap, ChevronLeft, ChevronRight, Filter, RefreshCw, CheckCircle, Clock } from 'lucide-react';
import { useRouter } from 'next/navigation';

// Mock data (You can replace this with facultyService.getAllFaculty() later if needed)
const facultyList = [
{
id: "f1",
name: "Dr. Rajesh Kumar",
department: "Computer Science",
specialization: "AI & Machine Learning",
projects: 5,
location: "Ettimadai",
avatar: null
},
{
id: "f2",
name: "Prof. Meera Reddy",
department: "Electrical & Electronics",
specialization: "Renewable Energy & IoT",
projects: 3,
location: "Ettimadai",
avatar: null
},
{
id: "f3",
name: "Dr. Anand Sharma",
department: "Cybersecurity",
specialization: "Blockchain & Fintech",
projects: 4,
location: "Amritapuri",
avatar: null
}
];
import { facultyService } from '@/services/facultyService';
import toast, { Toaster } from 'react-hot-toast';

export default function FacultySearchPage() {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const [facultyList, setFacultyList] = useState<any[]>([]);
const [loading, setLoading] = useState(true);

// --- 1. Fetch Real Data ---
const loadFaculty = async () => {
setLoading(true);
try {
const data = await facultyService.getAllFaculty();
// Ensure backend returns: id, name, department, designation, status, status_source, cabin_number
setFacultyList(data);
toast.success("Directory Updated");
} catch (error) {
console.error("Failed to load faculty", error);
toast.error("Could not load faculty data");
} finally {
setLoading(false);
}
};

useEffect(() => {
loadFaculty();
}, []);

// --- 2. Filter Logic ---
const filteredList = facultyList.filter(f =>
(f.name || "").toLowerCase().includes(searchQuery.toLowerCase()) ||
(f.department || "").toLowerCase().includes(searchQuery.toLowerCase()) ||
(f.cabin_number || "").toLowerCase().includes(searchQuery.toLowerCase())
);

return (
// --- Full Screen Mobile Container ---
<div className="min-h-screen bg-[#F9F9F9] font-sans flex flex-col">
<Toaster position="top-center" />

{/* --- HEADER (Sticky) --- */}
{/* HEADER */}
<div className="bg-[#8C1515] text-white p-6 pt-12 pb-6 shadow-md z-10 sticky top-0 space-y-4">

{/* Title Row */}
<div className="flex items-center gap-3">
<button onClick={() => router.back()} className="p-1 hover:bg-white/10 rounded-full transition-colors">
<ChevronLeft size={24} />
Expand All @@ -56,74 +53,94 @@ export default function FacultySearchPage() {
<h1 className="text-xl font-black tracking-tight leading-none">Find Mentors</h1>
<p className="text-white/60 text-[10px] font-bold mt-1">Discover professors & research leads</p>
</div>
<button onClick={loadFaculty} className="ml-auto p-2 bg-white/10 rounded-full hover:bg-white/20">
<RefreshCw size={16} className={loading ? "animate-spin" : ""} />
</button>
</div>

{/* Search Bar (Integrated in Header) */}
{/* Search */}
<div className="flex gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={16} />
<input
type="text"
placeholder="Search faculty..."
placeholder="Search faculty, cabin..."
className="w-full pl-10 pr-4 py-2.5 bg-white rounded-xl text-sm font-bold text-gray-800 placeholder-gray-400 outline-none shadow-sm"
onChange={(e) => setSearchQuery(e.target.value)}
value={searchQuery}
/>
</div>
<button className="bg-[#6b1010] p-2.5 rounded-xl text-white border border-white/20 shadow-sm">
<Filter size={18} />
</button>
</div>
</div>

{/* --- CONTENT --- */}
{/* CONTENT */}
<div className="flex-1 p-4 pb-10 space-y-4 overflow-y-auto">

{/* Faculty Grid (Single column on mobile) */}
{loading && (
<div className="flex justify-center mt-10">
<p className="text-gray-400 text-xs font-bold animate-pulse">Loading Directory...</p>
</div>
)}

{!loading && filteredList.length === 0 && (
<div className="text-center mt-10 text-gray-400 text-xs font-bold">No faculty found.</div>
)}

{/* List Grid */}
<div className="grid grid-cols-1 gap-4">
{facultyList.map((faculty) => (
<Link
{filteredList.map((faculty) => (
<a
key={faculty.id}
href={`/dashboard/student/faculty/${faculty.id}`}
className="bg-white border border-gray-100 rounded-2xl p-5 shadow-sm active:scale-[0.98] transition-transform block"
className="bg-white border border-gray-100 rounded-2xl p-5 shadow-sm active:scale-[0.98] transition-transform block relative overflow-hidden group"
>
<div className="flex items-start justify-between mb-3">
{/* Status Strip */}
<div className={`absolute left-0 top-0 bottom-0 w-1.5 ${
faculty.status === 'Available' ? 'bg-green-500' :
faculty.status === 'Busy' ? 'bg-red-500' : 'bg-yellow-500'
}`}></div>

<div className="flex items-start justify-between mb-3 pl-2">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center text-[#8C1515] font-black text-lg border-2 border-white shadow-sm">
{faculty.name.split(' ')[1]?.[0] || faculty.name[0]}
<div className="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center text-[#8C1515] font-black text-lg border-2 border-white shadow-sm overflow-hidden">
{faculty.profile_picture ? <img src={faculty.profile_picture} alt="profile" className="w-full h-full object-cover"/> : (faculty.name ? faculty.name[0] : "F")}
</div>
<div>
<h3 className="font-black text-gray-800 text-sm leading-tight">
<h3 className="font-black text-gray-800 text-sm leading-tight group-hover:text-[#8C1515] transition-colors">
{faculty.name}
</h3>
<p className="text-[10px] text-[#D4AF37] font-bold uppercase tracking-wider mt-0.5">
{faculty.department}
<p className="text-[10px] text-gray-500 font-bold uppercase tracking-wider mt-0.5">
{faculty.department} • {faculty.designation}
</p>
</div>
</div>
<div className="bg-red-50 text-[#8C1515] text-[9px] font-black px-2 py-1 rounded-lg uppercase border border-red-100">
{faculty.projects} Openings

{/* Status Badge */}
<div className={`px-2 py-1 rounded-lg border text-[9px] font-black uppercase flex items-center gap-1 ${
faculty.status === 'Available' ? 'bg-green-50 text-green-700 border-green-100' :
faculty.status === 'Busy' ? 'bg-red-50 text-red-700 border-red-100' : 'bg-yellow-50 text-yellow-700 border-yellow-100'
}`}>
{faculty.status === 'Available' ? <CheckCircle size={10} /> : <Clock size={10} />}
{faculty.status || 'Unknown'}
</div>
</div>

<div className="space-y-2 mb-4 pl-1">
<div className="flex items-center gap-2 text-gray-500 text-xs font-medium">
<GraduationCap size={14} className="text-[#8C1515]" />
<span>{faculty.specialization}</span>
</div>
<div className="flex items-center gap-2 text-gray-500 text-xs font-medium">
<MapPin size={14} className="text-[#8C1515]" />
<span>{faculty.location}</span>
</div>
<div className="space-y-2 mb-4 pl-3">
{faculty.cabin_number && (
<div className="flex items-center gap-2 text-gray-500 text-xs font-medium">
<MapPin size={14} className="text-[#8C1515]" />
<span>Cabin: {faculty.cabin_number}</span>
</div>
)}
</div>

<div className="pt-3 border-t border-gray-50 flex items-center justify-between text-[#8C1515] font-black text-xs uppercase tracking-wide">
View Profile
<div className="pt-3 border-t border-gray-50 flex items-center justify-between text-[#8C1515] font-black text-xs uppercase tracking-wide pl-2">
View Locator & Status
<ChevronRight size={16} />
</div>
</Link>
</a>
))}
</div>

</div>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
-ms-overflow-style: none;
scrollbar-width: none;
}


}
95 changes: 95 additions & 0 deletions src/components/dashboard/StatusMapManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client";

import React, { useState, useEffect } from 'react';
import { MapPin, CheckCircle, Clock, Sparkles, Navigation, RefreshCw } from 'lucide-react';
import { locatorService } from '@/services/locatorService';
import InteractiveMap from '@/components/interactiveMap';
import toast from 'react-hot-toast';

export default function StatusMapManager({ facultyId }: { facultyId: string }) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState<any>(null);
const [updating, setUpdating] = useState(false);

useEffect(() => {
loadData();
}, [facultyId]);

const loadData = async () => {
try {
const res = await locatorService.getFacultyLocation(facultyId);
setData(res);
} catch (error) {
console.error("Failed to load locator data", error);
} finally {
setLoading(false);
}
};

const changeStatus = async (newStatus: string) => {
setUpdating(true);
try {
await locatorService.updateStatus(facultyId, newStatus, 'Manual');
setData((prev: any) => ({
...prev,
status: { ...prev.status, current: newStatus, source: 'Manual' }
}));
toast.success(`Status updated to ${newStatus}`);
} catch (error) {
toast.error("Failed to update status");
} finally {
setUpdating(false);
}
};

if (loading) return <div className="p-4 text-xs text-gray-400 animate-pulse">Loading Live Status...</div>;
if (!data) return null;

const currentStatus = data.status?.current || 'Available';
const location = data.location || {};

return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">

{/* 1. LIVE STATUS CONTROLLER */}
<div className="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-100">
<h3 className="text-[#8C1515] font-black text-sm uppercase tracking-widest mb-4 flex items-center gap-2">
<Sparkles size={16} /> Live Availability
</h3>

<div className="flex items-center gap-4 mb-6">
<div className={`w-16 h-16 rounded-2xl flex items-center justify-center text-2xl shadow-inner ${
currentStatus === 'Available' ? 'bg-green-100 text-green-600' :
currentStatus === 'Busy' ? 'bg-red-100 text-red-600' : 'bg-yellow-100 text-yellow-600'
}`}>
{currentStatus === 'Available' ? <CheckCircle /> : <Clock />}
</div>
<div>
<p className="text-xs text-gray-400 font-bold uppercase">Current Status</p>
<h2 className="text-xl font-black text-gray-800">{currentStatus}</h2>
<p className="text-[10px] text-gray-400 font-medium">via {data.status?.source}</p>
</div>
</div>

<div className="grid grid-cols-3 gap-2">
{['Available', 'Busy', 'In Class'].map((status) => (
<button
key={status}
onClick={() => changeStatus(status)}
disabled={updating || currentStatus === status}
className={`py-3 rounded-xl text-[10px] font-bold uppercase transition-all ${
currentStatus === status
? 'bg-gray-800 text-white shadow-lg scale-[1.02]'
: 'bg-gray-50 text-gray-500 hover:bg-gray-100'
}`}
>
{status}
</button>
))}
</div>
</div>


</div>
);
}
Loading