import React, { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { supabase } from '../../supabaseClient';
import FullProfileOverlay from '../FullProfileOverlay';
import ScheduleInterviewModal from '../ScheduleInterviewModal';
// --- ICONS ---
const SmallCalendarIcon = () => ();
const ChevronRightIcon = () => ();
const CloseIcon = () => ;
const SendIcon = () => ;
// --- NEW FULL CHAT UI MODAL ---
const AdminChatModal = ({ isOpen, onClose, applicant }) => {
const [messages, setMessages] = useState([]);
const [text, setText] = useState('');
const [loading, setLoading] = useState(true);
const [adminId, setAdminId] = useState(null);
const messagesEndRef = useRef(null);
// Fetch chat history
useEffect(() => {
if (!isOpen || !applicant?.userId) return;
const fetchMessages = async () => {
setLoading(true);
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
setAdminId(user.id);
// Fetch conversation strictly between this Admin and this Applicant
const { data, error } = await supabase
.from('messages')
.select('*')
.or(`and(sender_id.eq.${user.id},receiver_id.eq.${applicant.userId}),and(sender_id.eq.${applicant.userId},receiver_id.eq.${user.id})`)
.order('created_at', { ascending: true });
if (!error && data) {
setMessages(data);
}
} catch (err) {
console.error("Error fetching chat:", err);
} finally {
setLoading(false);
}
};
fetchMessages();
}, [isOpen, applicant]);
// Auto-scroll to bottom of chat
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMsg = async () => {
if (!text.trim() || !adminId || !applicant?.userId) return;
const newMsg = {
id: Date.now(), // Temporary ID for optimistic UI update
sender_id: adminId,
receiver_id: applicant.userId,
content: text.trim(),
created_at: new Date().toISOString()
};
// Update UI instantly
setMessages(prev => [...prev, newMsg]);
setText('');
// Send to database
const { error } = await supabase.from('messages').insert([{
sender_id: adminId,
receiver_id: applicant.userId,
content: newMsg.content
}]);
if (error) console.error("Error sending message:", error);
};
const unsendMsg = async (msgId) => {
if (!window.confirm("Unsend this message?")) return;
// 1. Try to delete the message
const { data, error } = await supabase.from('messages').delete().eq('id', msgId).select();
if (error) {
console.error("Unsend Error:", error);
alert("Failed to unsend message.");
return;
}
// 2. If RLS silently blocks deletion, fallback to update
if (!data || data.length === 0) {
const { error: updateError } = await supabase
.from('messages')
.update({ content: "🚫 This message was unsent" })
.eq('id', msgId);
if (updateError) {
alert("Database policies prevent unsending this message.");
return;
}
setMessages(prev => prev.map(m => m.id === msgId ? { ...m, content: "🚫 This message was unsent" } : m));
} else {
setMessages(prev => prev.filter(m => m.id !== msgId));
}
};
const canUnsend = (rawTime) => {
if (!rawTime) return false;
const msgTime = new Date(rawTime).getTime();
return (Date.now() - msgTime) < 5 * 60 * 1000; // 5 minutes
};
if (!isOpen || !applicant) return null;
return (
{/* Chat Header */}
{applicant.name}
{applicant.role}
{/* Chat History */}
{loading ? (
Loading conversation...
) : messages.length === 0 ? (
No previous messages. Start the conversation!
) : (
messages.map(m => {
const isMe = m.sender_id === adminId;
return (
{m.content}
{new Date(m.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{isMe && canUnsend(m.created_at) && (
)}
);
})
)}
{/* Input Area */}
);
};
// --- MAIN COMPONENT ---
export default function AdminInterviewManagement() {
const [activeSubTab, setActiveSubTab] = useState('interviews');
const [loading, setLoading] = useState(true);
const [applicants, setApplicants] = useState({ interviews: [], accepted: [], rejected: [] });
// Modals State
const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false);
const [isMessageModalOpen, setIsMessageModalOpen] = useState(false);
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [selectedApplicant, setSelectedApplicant] = useState(null);
const [drawerCandidate, setDrawerCandidate] = useState(null);
// ✅ FIXED: Marked this function as 'async' to resolve the Syntax Error
const fetchData = async () => {
try {
setLoading(true);
// Join applications with profiles, jobs, and CHECK interviews table
const { data, error } = await supabase
.from('applications')
.select(`
id, user_id, job_id, created_at, status, experience, skills, match_score, resume_url,
profiles ( id, full_name, email, avatar_url, phone, location, summary, headline, current_position, education, work_experience, experience_years, projects, skills, technical_skills, resume_url ),
jobs ( id, title ),
interviews ( id, date, time, status, created_at )
`)
.order('created_at', { ascending: false });
if (error) throw error;
const categorized = { interviews: [], accepted: [], rejected: [] };
data.forEach(app => {
// Check if an interview row exists for this application (Get the LATEST one)
const interviews = app.interviews || [];
interviews.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
const interviewData = interviews.length > 0 ? interviews[0] : null;
// Combine skills and technical_skills
const profileSkills = Array.isArray(app.profiles?.skills) ? app.profiles.skills : [];
const profileTechSkills = Array.isArray(app.profiles?.technical_skills) ? app.profiles.technical_skills : [];
const parsedTechSkills = (typeof app.profiles?.technical_skills === 'string')
? app.profiles.technical_skills.split(',').map(s => s.trim())
: profileTechSkills;
const combinedSkills = [...new Set([...profileSkills, ...parsedTechSkills])];
const finalSkills = combinedSkills.length > 0
? combinedSkills
: (Array.isArray(app.skills) ? app.skills : (app.skills ? [app.skills] : []));
const formattedApp = {
...app,
full_name: app.profiles?.full_name,
email: app.profiles?.email,
phone: app.profiles?.phone,
location: app.profiles?.location,
summary: app.profiles?.summary,
headline: app.profiles?.headline,
current_position: app.profiles?.current_position,
education: app.profiles?.education,
work_experience: app.profiles?.work_experience,
projects: app.profiles?.projects,
name: app.profiles?.full_name || 'Unknown User',
role: app.jobs?.title || 'Unknown Role',
avatar: app.profiles?.avatar_url,
resumeUrl: app.resume_url || app.profiles?.resume_url,
userId: app.profiles?.id || app.user_id,
jobId: app.job_id,
experience: (app.experience === '0' || app.experience === 0)
? 'Fresher'
: (app.experience ? `${app.experience} years` : 'N/A'),
skills: finalSkills,
interviewId: interviewData?.id,
date: interviewData ? interviewData.date : 'Not Scheduled',
time: interviewData ? interviewData.time : '',
};
// --- SORTING LOGIC ---
if (interviewData) {
categorized.interviews.push(formattedApp);
} else if (app.status === 'Accepted' || app.status === 'Approved') {
categorized.accepted.push(formattedApp);
} else if (app.status === 'Rejected') {
categorized.rejected.push(formattedApp);
}
});
setApplicants(categorized);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
// 1. Fetch Data on Component Mount
useEffect(() => {
fetchData();
}, []);
// 2. Updated Schedule Handler
// ✅ Helper: Send message to candidate
const sendMessageToCandidate = async (candidateUserId, message) => {
try {
const { data: { user: adminUser } } = await supabase.auth.getUser();
if (!adminUser) return;
const { error } = await supabase.from('messages').insert([{
sender_id: adminUser.id,
receiver_id: candidateUserId,
content: message,
is_read: false
}]);
if (error) console.error('Error sending message:', error);
} catch (err) {
console.error('Failed to send message:', err);
}
};
const handleScheduleConfirm = async (scheduleData) => {
if (!selectedApplicant) return;
const { date, time, interviewType, mode, details, interviewerName, interviewerRole } = scheduleData;
try {
const scheduledTimeISO = new Date(`${date}T${time}:00`).toISOString();
const interviewPayload = {
application_id: selectedApplicant.id,
scheduled_time: scheduledTimeISO,
date, time,
status: 'Scheduled',
interview_type: interviewType,
mode: mode,
meeting_link: mode === 'Online' ? details : null,
location: mode === 'Offline' ? details : null,
interviewer_name: interviewerName,
interviewer_role: interviewerRole,
duration_mins: 45
};
let dbError;
if (selectedApplicant.interviewId) {
const { error } = await supabase
.from('interviews')
.update(interviewPayload)
.eq('id', selectedApplicant.interviewId);
dbError = error;
} else {
const { error } = await supabase
.from('interviews')
.insert([interviewPayload]);
dbError = error;
}
if (dbError) throw dbError;
// ✅ Send interview scheduled message to candidate
const interviewDatesTime = `${date} at ${time}`;
const modeInfo = mode === 'Online' ? `via ${details}` : `at ${details}`;
const jobContext = selectedApplicant.role ? ` for ${selectedApplicant.role}` : '';
await sendMessageToCandidate(
selectedApplicant.userId,
`📅 Great news! Your interview${jobContext} has been scheduled for ${interviewDatesTime} (${interviewType}) ${modeInfo}. Interviewer: ${interviewerName} (${interviewerRole}). Please confirm your availability.`
);
if (selectedApplicant.email) {
await supabase.functions.invoke('send-interview-email', {
body: {
candidateName: selectedApplicant.name,
candidateEmail: selectedApplicant.email,
role: selectedApplicant.role,
date, time, mode, details
}
});
}
alert("Interview Scheduled Successfully and candidate notified!");
setIsScheduleModalOpen(false);
fetchData();
} catch (error) {
console.error("Error scheduling:", error);
alert(`Failed to schedule: ${error.message}`);
}
};
const openScheduleModal = (applicant) => { setSelectedApplicant(applicant); setIsScheduleModalOpen(true); };
const openMessageModal = (applicant) => { setSelectedApplicant(applicant); setIsMessageModalOpen(true); };
const openDrawer = (applicant) => { setDrawerCandidate(applicant); setIsDrawerOpen(true); };
const primaryButtonStyle = { backgroundColor: '#EF4444', color: 'white', padding: '0.5rem 1rem', borderRadius: '9999px', fontWeight: '500', cursor: 'pointer', border: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%' };
const secondaryButtonStyle = { backgroundColor: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.2)', color: 'white', padding: '0.5rem 1rem', borderRadius: '9999px', fontWeight: '500', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%' };
return (
{/* Tabs */}
{loading ? (Loading...
) : applicants[activeSubTab].length === 0 ? (No candidates found.
) : (
{applicants[activeSubTab].map((applicant, index) => (
{/* INFO SECTION */}
{applicant.name}
{applicant.role} • {applicant.experience}
{activeSubTab === 'interviews' && applicant.time && (
Interview: {applicant.date} at {applicant.time}
)}
{/* BUTTONS SECTION */}
{activeSubTab === 'interviews' && (<>
openScheduleModal(applicant)} whileHover={{ scale: 1.02 }} style={secondaryButtonStyle}>Reschedule
openMessageModal(applicant)} whileHover={{ scale: 1.02 }} style={secondaryButtonStyle}>Message
openDrawer(applicant)} whileHover={{ scale: 1.02 }} style={primaryButtonStyle}>View CV
>)}
{activeSubTab === 'accepted' && (<>
openScheduleModal(applicant)} whileHover={{ scale: 1.02 }} style={secondaryButtonStyle}>Schedule Interview
openMessageModal(applicant)} whileHover={{ scale: 1.02 }} style={secondaryButtonStyle}>Send Message
openDrawer(applicant)} whileHover={{ scale: 1.02 }} style={primaryButtonStyle}>View CV
>)}
{activeSubTab === 'rejected' && (<>
openMessageModal(applicant)} whileHover={{ scale: 1.02 }} style={secondaryButtonStyle}>Send Message
openDrawer(applicant)} whileHover={{ scale: 1.02 }} style={primaryButtonStyle}>View CV
>)}
))}
)}
{/* --- MODALS --- */}
{isScheduleModalOpen && (
setIsScheduleModalOpen(false)}
onConfirm={handleScheduleConfirm}
candidateName={selectedApplicant?.name}
/>
)}
{isMessageModalOpen && (
setIsMessageModalOpen(false)}
applicant={selectedApplicant}
/>
)}
{isDrawerOpen && (
setIsDrawerOpen(false)}
/>
)}
);
}