Drawer opens and closes
This commit is contained in:
parent
4330bd4b7c
commit
8f6c39c3f7
@ -15,8 +15,16 @@ import {
|
||||
Card,
|
||||
CardContent,
|
||||
Avatar,
|
||||
Grid
|
||||
Drawer,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Fab
|
||||
} from '@mui/material';
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Chat as ChatIcon
|
||||
} from '@mui/icons-material';
|
||||
import { useUser } from 'hooks/useUser';
|
||||
import { ChatMessageBase, ChatMessage, ChatSession } from 'types/types';
|
||||
import { ConversationHandle } from 'components/Conversation';
|
||||
@ -27,13 +35,20 @@ import { CandidateSessionsResponse } from 'services/api-client';
|
||||
import { CandidateInfo } from 'components/CandidateInfo';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const DRAWER_WIDTH = 300;
|
||||
const HANDLE_WIDTH = 48;
|
||||
|
||||
const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((props: BackstoryPageProps, ref) => {
|
||||
const { apiClient, candidate } = useUser();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMdUp = useMediaQuery(theme.breakpoints.up('md'));
|
||||
|
||||
const {
|
||||
setSnack,
|
||||
submitQuery,
|
||||
} = props;
|
||||
|
||||
const [sessions, setSessions] = useState<CandidateSessionsResponse | null>(null);
|
||||
const [chatSession, setChatSession] = useState<ChatSession | null>(null);
|
||||
const [messages, setMessages] = useState([]);
|
||||
@ -42,6 +57,14 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((pr
|
||||
const [streaming, setStreaming] = useState(false);
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
// Drawer state - defaults to open on md+ screens or when no session is selected
|
||||
const [drawerOpen, setDrawerOpen] = useState(() => isMdUp || !chatSession);
|
||||
|
||||
// Update drawer state when screen size or session changes
|
||||
useEffect(() => {
|
||||
setDrawerOpen(isMdUp || !chatSession);
|
||||
}, [isMdUp, chatSession]);
|
||||
|
||||
// Load sessions for the candidate
|
||||
const loadSessions = async () => {
|
||||
if (!candidate) return;
|
||||
@ -156,69 +179,144 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((pr
|
||||
navigate('/find-a-candidate');
|
||||
}
|
||||
|
||||
const drawerContent = (
|
||||
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Chat Sessions
|
||||
{sessions && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${sessions.sessions.total} total`}
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={createNewSession}
|
||||
disabled={loading || !candidate}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
New Session
|
||||
</Button>
|
||||
|
||||
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
|
||||
{sessions ? (
|
||||
<List>
|
||||
{sessions.sessions.data.map((session: any) => (
|
||||
<ListItem
|
||||
key={session.id}
|
||||
onClick={() => {
|
||||
setChatSession(session);
|
||||
// Auto-close drawer on smaller screens when session is selected
|
||||
if (!isMdUp) {
|
||||
setDrawerOpen(false);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
mb: 1,
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: chatSession?.id === session.id ? 'primary.main' : 'divider',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={session.title}
|
||||
secondary={`${new Date(session.lastActivity).toLocaleDateString()} • ${session.context.type}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<Typography color="text.secondary" align="center">
|
||||
Enter a username and click "Load Sessions"
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const drawerHandle = (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
left: drawerOpen ? DRAWER_WIDTH : 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create('left', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.standard,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Fab
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => setDrawerOpen(!drawerOpen)}
|
||||
sx={{
|
||||
borderRadius: '0 50% 50% 0',
|
||||
width: 32,
|
||||
height: 64,
|
||||
minHeight: 64,
|
||||
boxShadow: 2,
|
||||
'&:hover': {
|
||||
boxShadow: 4,
|
||||
}
|
||||
}}
|
||||
>
|
||||
{drawerOpen ? <ChevronLeft /> : <ChatIcon />}
|
||||
</Fab>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box ref={ref} sx={{ width: "100%", height: "100%", display: "flex", flexGrow: 1, flexDirection: "column" }}>
|
||||
{candidate && <CandidateInfo action={`Chat with Backstory about ${candidate.firstName}`} elevation={4} candidate={candidate} sx={{ minHeight: "max-content" }} />}
|
||||
|
||||
< Box sx={{ display: "flex", mt: 1, gap: 1, height: "100%" }}>
|
||||
{/* Sessions Sidebar */}
|
||||
<Paper sx={{ p: 2, height: '100%', minWidth: { sm: "200px", md: "300px", lg: "400px" }, display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Chat Sessions
|
||||
{sessions && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${sessions.sessions.total} total`}
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
)}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", mt: 1, gap: 1, height: "100%", position: 'relative' }}>
|
||||
{/* Drawer */}
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
anchor="left"
|
||||
open={drawerOpen}
|
||||
sx={{
|
||||
width: drawerOpen ? DRAWER_WIDTH : 0,
|
||||
flexShrink: 0,
|
||||
'& .MuiDrawer-paper': {
|
||||
width: DRAWER_WIDTH,
|
||||
boxSizing: 'border-box',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
borderRight: '1px solid',
|
||||
borderColor: 'divider',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{drawerContent}
|
||||
</Drawer>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={createNewSession}
|
||||
disabled={loading || !candidate}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
New Session
|
||||
</Button>
|
||||
|
||||
<Box sx={{ flexGrow: 1, overflow: 'auto' }}>
|
||||
{sessions ? (
|
||||
<List>
|
||||
{sessions.sessions.data.map((session: any) => (
|
||||
<ListItem
|
||||
key={session.id}
|
||||
// selected={chatSession?.id === session.id}
|
||||
onClick={() => setChatSession(session)}
|
||||
sx={{
|
||||
mb: 1,
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: chatSession?.id === session.id ? 'primary.main' : 'divider',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.hover'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={session.title}
|
||||
secondary={`${new Date(session.lastActivity).toLocaleDateString()} • ${session.context.type}`}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
<Typography color="text.secondary" align="center">
|
||||
Enter a username and click "Load Sessions"
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
{/* Drawer Handle */}
|
||||
{drawerHandle}
|
||||
|
||||
{/* Chat Interface */}
|
||||
<Paper sx={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.standard,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{chatSession?.id ? (
|
||||
<>
|
||||
{/* Messages Area */}
|
||||
|
Loading…
x
Reference in New Issue
Block a user