File "DriverAttendantChatPage.tsx"
Full Path: /home/trinadezambia/public_html/student_panel/src/components/chat/DriverAttendantChatPage.tsx
File size: 17.79 KB
MIME-type: text/x-java
Charset: utf-8
'use client';
// components/chat/DriverAttendantChatPage.tsx
import React, { useState, useEffect } from 'react';
import { ChatWindow } from './ChatWindow';
import Breadcrumb from '../ui/pages/dashboard/Breadcrumb';
import { BiHomeSmile, BiBus } from 'react-icons/bi';
import { useSelector } from 'react-redux';
import { RootState } from '../store';
import {
useGetTransportationDashboard,
useGetChatHistory,
} from '@/lib/api/student/queryHooks';
import Image from 'next/image';
import { useSearchParams, useRouter } from 'next/navigation';
import { useTranslate } from '@/components/hooks/useTranslate';
// Define the contact type (same as in ChatPage)
type Contact = {
id: number;
name: string;
lastMessage: string;
time: string;
avatar: string;
unread: number;
active: boolean;
};
const DriverAttendantChatPage = () => {
const translate = useTranslate();
// State for managing selected contact and mobile view
const [selectedContact, setSelectedContact] = useState<Contact | null>(null);
const [showChatWindow, setShowChatWindow] = useState(false);
// Get search params and router to manage URL parameters
const searchParams = useSearchParams();
const router = useRouter();
const contactType = searchParams.get('contact'); // '1' for driver, '2' for attendant
// Get user data from Redux store
const { user } = useSelector((state: RootState) => state.studentAuth);
const userId = user?.id;
// Fetch transportation dashboard data to get driver and attender information
const {
data: dashboardData,
isLoading: isDashboardLoading,
error: dashboardError,
} = useGetTransportationDashboard(
userId ? { user_id: userId, pickup_drop: 0 } : null
);
// Fetch chat history for Driver and Attendant roles
const { data: driverChatHistoryResponse, isLoading: isLoadingDriverChat } =
useGetChatHistory('Driver');
const {
data: attendantChatHistoryResponse,
isLoading: isLoadingAttendantChat,
} = useGetChatHistory('Attendant');
const breadcrumbItems = [
{
label: translate('home'),
href: '/student/dashboard',
icon: <BiHomeSmile className="w-5 h-5" />,
},
{
label: translate('transportation'),
href: '/student/transportation',
icon: <BiBus className="w-5 h-5" />,
},
{
label: translate('driverAttenderChat'),
},
];
// Handle contact selection with URL update
const handleContactSelect = (
contact: Contact,
contactTypeParam: '1' | '2'
) => {
setSelectedContact(contact);
setShowChatWindow(true);
// Update URL parameter based on selected contact
router.replace(
`/student/chats/driver-attendant-chat?contact=${contactTypeParam}`
);
};
// Handle back to contact list on mobile
const handleBackToContacts = () => {
setShowChatWindow(false);
setSelectedContact(null);
};
// Utility function to extract time from date-time string
// This function matches the exact implementation from ChatSidebar.tsx
const extractTime = (dateTimeString: string): string => {
// Handle null, undefined, or empty strings
if (!dateTimeString || dateTimeString.trim() === '') {
return 'now';
}
try {
// First, try the original ChatSidebar logic for "DD/MM/YYYY HH:MM" format
const parts = dateTimeString.split(' ');
if (parts.length > 1 && parts[1].includes(':')) {
// This handles "26/09/2025 11:52" format
return parts[1];
}
// If that doesn't work, try parsing as ISO date
const date = new Date(dateTimeString);
if (!isNaN(date.getTime())) {
// If it's a valid ISO date, format it as HH:MM
return date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
}
// Last fallback
return 'now';
} catch (error) {
console.warn('Error parsing date:', dateTimeString, error);
return 'now';
}
};
// Create contact objects from chat history data
const createDriverContact = (): Contact | null => {
// First try to get from chat history (actual chat users) - but only if not loading
if (!isLoadingDriverChat) {
// API now returns paginated object, so we need to access .data property
const driverChatHistoryData = driverChatHistoryResponse?.data;
const driverChatHistory =
driverChatHistoryData && Array.isArray(driverChatHistoryData.data)
? driverChatHistoryData.data
: [];
if (driverChatHistory.length > 0) {
// Use the first driver from chat history
const driverChat = driverChatHistory[0];
// Debug: Log the actual date format from API
console.log('Driver chat updated_at:', driverChat.updated_at);
return {
id: driverChat.user.id,
name: driverChat.user.full_name,
lastMessage:
driverChat.last_message || translate('startConversation'),
time: extractTime(driverChat.updated_at || ''),
avatar:
driverChat.user.image ||
driverChat.user.full_name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase(),
unread: driverChat.unread_count,
active: false,
};
}
}
// Fallback to transportation data if no chat history found or still loading
if (!dashboardData?.data?.bus_info?.driver) return null;
const driver = dashboardData.data.bus_info.driver;
return {
id: driver.id,
name: driver.name,
lastMessage: translate('startConversation'),
time: 'now',
avatar:
driver.avtar ||
driver.name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase(),
unread: 0,
active: false,
};
};
const createAttenderContact = (): Contact | null => {
// First try to get from chat history (actual chat users) - but only if not loading
if (!isLoadingAttendantChat) {
// API now returns paginated object, so we need to access .data property
const attendantChatHistoryData = attendantChatHistoryResponse?.data;
const attendantChatHistory =
attendantChatHistoryData && Array.isArray(attendantChatHistoryData.data)
? attendantChatHistoryData.data
: [];
if (attendantChatHistory.length > 0) {
// Use the first attendant from chat history
const attendantChat = attendantChatHistory[0];
// Debug: Log the actual date format from API
console.log('Attendant chat updated_at:', attendantChat.updated_at);
return {
id: attendantChat.user.id,
name: attendantChat.user.full_name,
lastMessage:
attendantChat.last_message || translate('startConversation'),
time: extractTime(attendantChat.updated_at || ''),
avatar:
attendantChat.user.image ||
attendantChat.user.full_name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase(),
unread: attendantChat.unread_count,
active: false,
};
}
}
// Fallback to transportation data if no chat history found or still loading
if (!dashboardData?.data?.bus_info?.attender) return null;
const attender = dashboardData.data.bus_info.attender;
return {
id: attender.id,
name: attender.name,
lastMessage: translate('startConversation'),
time: 'now',
avatar:
attender.avtar ||
attender.name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase(),
unread: 0,
active: false,
};
};
const driverContact = createDriverContact();
const attenderContact = createAttenderContact();
// Set contact based on URL parameter or default to driver
useEffect(() => {
if (!driverContact || !attenderContact) return;
if (contactType === '2') {
// URL parameter is '2' - select attendant
if (selectedContact?.id !== attenderContact.id) {
setSelectedContact(attenderContact);
}
} else if (contactType === '1') {
// URL parameter is '1' - select driver
if (selectedContact?.id !== driverContact.id) {
setSelectedContact(driverContact);
}
} else if (!selectedContact) {
// No URL parameter - default to driver
setSelectedContact(driverContact);
}
}, [driverContact, attenderContact, contactType, selectedContact]);
// Show loading state only on initial load (when we don't have any data yet)
if (isDashboardLoading && !dashboardData) {
return (
<>
<Breadcrumb
title={translate('driverAttenderChat')}
items={breadcrumbItems}
/>
<div className="flex h-screen font-sans text-gray-800 bg-white border border-gray-200 rounded-[12px] overflow-hidden">
<div className="flex items-center justify-center w-full">
<p className="text-gray-500">
{translate('loadingTransportationData')}
</p>
</div>
</div>
</>
);
}
// Show error state if API calls fail or no contacts available
if (dashboardError || (!driverContact && !attenderContact)) {
return (
<>
<Breadcrumb
title={translate('driverAttenderChat')}
items={breadcrumbItems}
/>
<div className="flex h-screen font-sans text-gray-800 bg-white border border-gray-200 rounded-[12px] overflow-hidden">
<div className="flex items-center justify-center w-full">
<div className="text-center">
<p className="text-red-500 mb-2">
{translate('noDriverOrAttendantAvailable')}
</p>
<p className="text-gray-500 text-sm">
{translate('checkTransportationAssignment')}
</p>
</div>
</div>
</div>
</>
);
}
return (
<>
<Breadcrumb
title={translate('driverAttenderChat')}
items={breadcrumbItems}
/>
{/* Desktop Layout - Show contact list and chat side by side */}
<div className="hidden md:flex h-[calc(100vh-120px)] font-sans text-gray-800 bg-white border border-gray-200 rounded-[12px] overflow-hidden">
{/* Contact List Sidebar - Simple UI */}
<aside className="w-80 border-r border-gray-200 bg-white flex flex-col">
{/* Header */}
<div className="p-4 border-b border-gray-200">
<h2 className="text-lg font-medium text-gray-800">
{translate('transportationStaff')}
</h2>
</div>
{/* Contact List */}
<div className="flex-1 overflow-y-auto">
<div className="p-2 space-y-1">
{/* Driver Contact */}
{driverContact && (
<div
onClick={() => handleContactSelect(driverContact, '1')}
className={`flex items-center p-3 rounded-lg cursor-pointer transition-colors hover:bg-gray-50 ${
selectedContact?.id === driverContact.id
? 'bg-blue-50 border border-(--primary-color)'
: ''
}`}
>
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center font-bold text-(--primary-color) mr-3 shrink-0">
{driverContact.avatar.startsWith('http') ? (
<Image
src={driverContact.avatar}
alt="Driver"
width={40}
height={40}
className="w-full h-full object-cover rounded-full"
/>
) : (
<span className="text-sm font-bold">
{driverContact.avatar}
</span>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-800 truncate">
{driverContact.name}
</h3>
<p className="text-sm text-(--primary-color)">
{translate('driver')}
</p>
</div>
</div>
)}
{/* Attender Contact */}
{attenderContact && (
<div
onClick={() => handleContactSelect(attenderContact, '2')}
className={`flex items-center p-3 rounded-lg cursor-pointer transition-colors hover:bg-gray-50 ${
selectedContact?.id === attenderContact.id
? 'bg-blue-50 border border-(--primary-color)'
: ''
}`}
>
<div className="w-10 h-10 rounded-full bg-(--primary-color) flex items-center justify-center font-bold text-white mr-3 shrink-0">
{attenderContact.avatar.startsWith('http') ? (
<Image
src={attenderContact.avatar}
alt="Attender"
width={40}
height={40}
className="w-full h-full object-cover rounded-full"
/>
) : (
<span className="text-sm font-bold">
{attenderContact.avatar}
</span>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-800 truncate">
{attenderContact.name}
</h3>
<p className="text-sm text-(--primary-color)">
{translate('attender')}
</p>
</div>
</div>
)}
</div>
</div>
</aside>
{/* Chat Window */}
<ChatWindow selectedContact={selectedContact} />
</div>
{/* Mobile Layout - Toggle between contact list and chat */}
<div className="md:hidden flex h-[calc(100vh-120px)] font-sans text-gray-800 bg-white border border-gray-200 rounded-[12px] overflow-hidden">
{!showChatWindow ? (
/* Mobile Contact List */
<div className="w-full flex flex-col">
{/* Header */}
<div className="p-4 border-b border-gray-200">
<h2 className="text-lg font-medium text-gray-800">
{translate('transportationStaff')}
</h2>
<p className="text-sm text-gray-500 mt-1">
{translate('selectContactToStartChatting')}
</p>
</div>
{/* Contact List */}
<div className="flex-1 overflow-y-auto p-2 space-y-1">
{/* Driver Contact */}
{driverContact && (
<div
onClick={() => handleContactSelect(driverContact, '1')}
className="flex items-center p-3 rounded-lg cursor-pointer transition-colors hover:bg-gray-50 active:bg-gray-100"
>
<div className="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center font-bold text-(--primary-color) mr-3 shrink-0">
{driverContact.avatar.startsWith('http') ? (
<Image
src={driverContact.avatar}
alt="Driver"
width={48}
height={48}
className="w-full h-full object-cover rounded-full"
/>
) : (
<span className="text-sm font-bold">
{driverContact.avatar}
</span>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-800 truncate">
{driverContact.name}
</h3>
<p className="text-sm text-(--primary-color)">
{translate('driver')}
</p>
</div>
</div>
)}
{/* Attender Contact */}
{attenderContact && (
<div
onClick={() => handleContactSelect(attenderContact, '2')}
className="flex items-center p-3 rounded-lg cursor-pointer transition-colors hover:bg-gray-50 active:bg-gray-100"
>
<div className="w-12 h-12 rounded-full bg-(--primary-color) flex items-center justify-center font-bold text-white mr-3 shrink-0">
{attenderContact.avatar.startsWith('http') ? (
<Image
src={attenderContact.avatar}
alt="Attender"
width={48}
height={48}
className="w-full h-full object-cover rounded-full"
/>
) : (
<span className="text-sm font-bold">
{attenderContact.avatar}
</span>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-800 truncate">
{attenderContact.name}
</h3>
<p className="text-sm text-(--primary-color)">
{translate('attender')}
</p>
</div>
</div>
)}
</div>
</div>
) : (
/* Mobile Chat Window */
<ChatWindow
selectedContact={selectedContact}
onBackToContacts={handleBackToContacts}
isMobile={true}
/>
)}
</div>
</>
);
};
export default DriverAttendantChatPage;