+
setNewDeptName(e.target.value)}
placeholder="Enter department name"
- className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
+ className="flex-1 px-4 py-3 text-base border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
onKeyPress={(e) => e.key === "Enter" && addDepartment()}
/>
Add
@@ -310,7 +306,7 @@ export default function AdminSettings() {
setShowDeptInput(false);
setNewDeptName("");
}}
- className="px-3 py-1.5 bg-gray-300 text-gray-700 text-sm rounded-md hover:bg-gray-400"
+ className="px-4 py-3 bg-gray-300 text-gray-700 text-base rounded-md hover:bg-gray-400"
>
Cancel
@@ -321,14 +317,14 @@ export default function AdminSettings() {
{departments.map((dept) => (
{dept.name}
removeDepartment(dept.id)}
className="text-gray-400 hover:text-red-500 transition-colors ml-1"
>
-
+
))}
@@ -337,14 +333,14 @@ export default function AdminSettings() {
{/* Save Button */}
-
);
-}
+}
\ No newline at end of file
diff --git a/frontend/src/features/admin/components/CandidateAvailability.tsx b/frontend/src/features/admin/components/CandidateAvailability.tsx
new file mode 100644
index 0000000..9bc47c0
--- /dev/null
+++ b/frontend/src/features/admin/components/CandidateAvailability.tsx
@@ -0,0 +1,388 @@
+import { useState } from "react";
+import { Search, User, ChevronRight } from "lucide-react";
+import CandidateCalendar from "./CandidateCalendar";
+import { Candidate, statusConfig } from "../types/index";
+
+const mockCandidates: Candidate[] = [
+ {
+ id: "1",
+ name: "Aisha Patel",
+ email: "aisha.patel@email.com",
+ role: "Software Engineer",
+ department: "Engineering",
+ status: "submitted",
+ availability: [
+ {
+ day: "Mon", date: "Jan 13",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Tue", date: "Jan 14",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Wed", date: "Jan 15",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Thu", date: "Jan 16",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Fri", date: "Jan 17",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ ],
+ },
+ {
+ id: "2",
+ name: "Marcus Chen",
+ email: "marcus.chen@email.com",
+ role: "Academic Coordinator",
+ department: "Academics",
+ status: "submitted",
+ availability: [
+ {
+ day: "Mon", date: "Jan 13",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Tue", date: "Jan 14",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Wed", date: "Jan 15",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Thu", date: "Jan 16",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Fri", date: "Jan 17",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ ],
+ },
+ {
+ id: "3",
+ name: "Sofia Reyes",
+ email: "sofia.reyes@email.com",
+ role: "Software Engineer",
+ department: "Engineering",
+ status: "pending",
+ availability: [],
+ },
+ {
+ id: "4",
+ name: "James Okafor",
+ email: "james.okafor@email.com",
+ role: "Software Engineer",
+ department: "Engineering",
+ status: "interviewed",
+ availability: [
+ {
+ day: "Mon", date: "Jan 13",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Tue", date: "Jan 14",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Wed", date: "Jan 15",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Thu", date: "Jan 16",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Fri", date: "Jan 17",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ ],
+ },
+ {
+ id: "5",
+ name: "Priya Nair",
+ email: "priya.nair@email.com",
+ role: "Academic Coordinator",
+ department: "Academics",
+ status: "submitted",
+ availability: [
+ {
+ day: "Mon", date: "Jan 13",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Tue", date: "Jan 14",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Wed", date: "Jan 15",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: false },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ {
+ day: "Thu", date: "Jan 16",
+ slots: [
+ { time: "9:00 AM", available: true },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: false },
+ { time: "1:00 PM", available: false },
+ { time: "2:00 PM", available: true },
+ { time: "3:00 PM", available: false },
+ ],
+ },
+ {
+ day: "Fri", date: "Jan 17",
+ slots: [
+ { time: "9:00 AM", available: false },
+ { time: "10:00 AM", available: true },
+ { time: "11:00 AM", available: true },
+ { time: "1:00 PM", available: true },
+ { time: "2:00 PM", available: false },
+ { time: "3:00 PM", available: true },
+ ],
+ },
+ ],
+ },
+];
+
+export default function CandidateAvailability() {
+ const [selectedCandidate, setSelectedCandidate] = useState
(null);
+ const [search, setSearch] = useState("");
+ const [filterStatus, setFilterStatus] = useState("all");
+ const [viewingAvailability, setViewingAvailability] = useState(false);
+
+ const filtered = mockCandidates.filter((c) => {
+ const matchesSearch =
+ c.name.toLowerCase().includes(search.toLowerCase()) ||
+ c.email.toLowerCase().includes(search.toLowerCase()) ||
+ c.role.toLowerCase().includes(search.toLowerCase());
+ const matchesStatus = filterStatus === "all" || c.status === filterStatus;
+ return matchesSearch && matchesStatus;
+ });
+
+ if (viewingAvailability && selectedCandidate) {
+ return (
+ setViewingAvailability(false)}
+ />
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
Candidate Availability
+
+ {mockCandidates.length} candidates total
+
+
+
+ {/* Search */}
+
+
+ setSearch(e.target.value)}
+ className="w-full pl-9 pr-4 py-3 bg-white border border-gray-200 rounded-lg text-base text-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-300"
+ />
+
+
+ {/* Filter Tabs */}
+
+ {["all", "submitted", "pending", "interviewed"].map((status) => (
+ setFilterStatus(status)}
+ className={`flex-1 py-2.5 text-sm font-medium rounded-md capitalize transition-colors ${
+ filterStatus === status
+ ? "bg-white text-gray-900 shadow-sm"
+ : "text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ {status}
+
+ ))}
+
+
+ {/* Candidate Cards */}
+
+ {filtered.map((candidate) => (
+
{
+ setSelectedCandidate(candidate);
+ setViewingAvailability(true);
+ }}
+ className="w-full text-left p-3 rounded-xl border transition-all bg-white border-gray-200 text-gray-900 hover:border-gray-300 hover:shadow-sm"
+ >
+
+
+
+
+
+
{candidate.name}
+
+ {candidate.role} · {candidate.department}
+
+
+ {candidate.email}
+
+
+
+
+ {candidate.availability.length > 0
+ ? `${candidate.availability.reduce((acc, d) => acc + d.slots.filter((s) => s.available).length, 0)} open slots`
+ : "No availability yet"}
+
+
+ {statusConfig[candidate.status].label}
+
+
+
+
+
+ ))}
+
+ {filtered.length === 0 && (
+
+ No candidates found
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/features/admin/components/CandidateCalendar.tsx b/frontend/src/features/admin/components/CandidateCalendar.tsx
new file mode 100644
index 0000000..b932d4b
--- /dev/null
+++ b/frontend/src/features/admin/components/CandidateCalendar.tsx
@@ -0,0 +1,122 @@
+import { ArrowLeft, Clock, CheckCircle, XCircle, User } from "lucide-react";
+import { Candidate, statusConfig } from "../types/index";
+
+interface CandidateDetailProps {
+ candidate: Candidate;
+ onBack: () => void;
+}
+
+export default function CandidateDetail({ candidate, onBack }: CandidateDetailProps) {
+ const totalAvailable = candidate.availability.reduce(
+ (acc, day) => acc + day.slots.filter((s) => s.available).length,
+ 0
+ );
+
+ return (
+
+ {/* Back Button */}
+
+
+ Back to Candidates
+
+
+ {/* Candidate Header */}
+
+
+
+
+
+
+
+
{candidate.name}
+
{candidate.email}
+
{candidate.role} · {candidate.department}
+
+
+
+
+
{totalAvailable}
+
Open slots
+
+
+
{candidate.availability.length}
+
Days submitted
+
+
+ {statusConfig[candidate.status].label}
+
+
+
+
+
+ {/* Availability Grid */}
+ {candidate.availability.length > 0 ? (
+
+
+
+
Weekly Availability
+
+
+ Available
+
+
+ Unavailable
+
+
+
+
+
+
+
+
+ Time
+ {candidate.availability.map((day) => (
+
+ {day.day}
+ {day.date}
+
+ ))}
+
+
+
+ {candidate.availability[0].slots.map((_, slotIndex) => (
+
+
+ {candidate.availability[0].slots[slotIndex].time}
+
+ {candidate.availability.map((day) => (
+
+ {day.slots[slotIndex].available ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ ))}
+
+ ))}
+
+
+
+
+ ) : (
+
+
+
+
+
No availability submitted
+
+ {candidate.name} hasn't submitted their availability yet.
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/features/admin/types/index.ts b/frontend/src/features/admin/types/index.ts
index ab0c014..b6b97e9 100644
--- a/frontend/src/features/admin/types/index.ts
+++ b/frontend/src/features/admin/types/index.ts
@@ -1 +1,26 @@
-//
\ No newline at end of file
+export interface TimeSlot {
+ time: string;
+ available: boolean;
+}
+
+export interface DayAvailability {
+ day: string;
+ date: string;
+ slots: TimeSlot[];
+}
+
+export interface Candidate {
+ id: string;
+ name: string;
+ email: string;
+ role: string;
+ department: string;
+ status: "pending" | "submitted" | "interviewed";
+ availability: DayAvailability[];
+}
+
+export const statusConfig = {
+ submitted: { label: "Submitted", color: "bg-green-100 text-green-700" },
+ pending: { label: "Pending", color: "bg-yellow-100 text-yellow-700" },
+ interviewed: { label: "Interviewed", color: "bg-blue-100 text-blue-700" },
+};
\ No newline at end of file
diff --git a/frontend/src/features/dashboard/Dashboard.tsx b/frontend/src/features/dashboard/Dashboard.tsx
index b8dffb8..b535369 100644
--- a/frontend/src/features/dashboard/Dashboard.tsx
+++ b/frontend/src/features/dashboard/Dashboard.tsx
@@ -7,9 +7,16 @@ import {
Availability,
} from "./components";
import { AdminSettings } from "../admin/components";
+import AddInterviewers from "../admin/components/AddInterviewers";
+import CandidateAvailability from "../admin/components/CandidateAvailability";
+import { useContext } from "react";
+import { AuthContext } from "@/features/auth/services/AuthContext";
+import { UserRole } from "@/features/auth/types/authTypes";
export default function Dashboard() {
const [activePage, setActivePage] = useState("dashboard");
+ const auth = useContext(AuthContext);
+ const isCandidate = auth?.user?.role === UserRole.CANDIDATE;
return (
@@ -30,9 +37,13 @@ export default function Dashboard() {
) : activePage === "availability" ? (
+ ) : activePage === "add-interviewers" && !isCandidate ? (
+
+ ) : activePage === "candidate-availability" && !isCandidate ? (
+
) : null}
);
-}
+}
\ No newline at end of file
diff --git a/frontend/src/features/dashboard/components/Sidebar.tsx b/frontend/src/features/dashboard/components/Sidebar.tsx
index 6dffbdd..7bacc64 100644
--- a/frontend/src/features/dashboard/components/Sidebar.tsx
+++ b/frontend/src/features/dashboard/components/Sidebar.tsx
@@ -1,4 +1,4 @@
-import { CalendarDays, LayoutDashboard, Users, Settings, Plus } from "lucide-react";
+import { CalendarDays, LayoutDashboard, Users, Settings, Plus, UserPlus } from "lucide-react";
import { useContext } from "react";
import { AuthContext } from "@/features/auth/services/AuthContext";
import { UserRole } from "@/features/auth/types/authTypes";
@@ -61,13 +61,19 @@ export default function DashboardSidebar({ activePage = "dashboard", onPageChang