backstory/frontend/src/documents/UserManagement.tsx

508 lines
17 KiB
TypeScript

import React, { useState } from 'react';
import {
Box,
Typography,
Paper,
Tabs,
Tab,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Chip,
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
MenuItem,
Select,
FormControl,
InputLabel,
Grid
} from '@mui/material';
import { Person, Business, AssignmentInd } from '@mui/icons-material';
// Interfaces from the data model
interface BaseUser {
id: string;
email: string;
createdAt: Date;
lastLogin: Date;
profileImage?: string;
isActive: boolean;
}
interface Candidate extends BaseUser {
type: 'candidate';
firstName: string;
lastName: string;
skills: { id: string; name: string; level: string }[];
location: { city: string; country: string; remote?: boolean };
}
interface Employer extends BaseUser {
type: 'employer';
companyName: string;
industry: string;
companySize: string;
location: { city: string; country: string };
companyLogo?: string;
}
type User = Candidate | Employer;
// Mock data
const mockUsers: User[] = [
{
id: '1',
email: 'john.doe@example.com',
createdAt: new Date('2023-08-15'),
lastLogin: new Date('2023-10-22'),
isActive: true,
type: 'candidate',
firstName: 'John',
lastName: 'Doe',
skills: [
{ id: 's1', name: 'React', level: 'advanced' },
{ id: 's2', name: 'TypeScript', level: 'intermediate' }
],
location: { city: 'Austin', country: 'USA' }
},
{
id: '2',
email: 'sarah.smith@example.com',
createdAt: new Date('2023-09-10'),
lastLogin: new Date('2023-10-24'),
isActive: true,
type: 'candidate',
firstName: 'Sarah',
lastName: 'Smith',
skills: [
{ id: 's3', name: 'Python', level: 'expert' },
{ id: 's4', name: 'Data Science', level: 'advanced' }
],
location: { city: 'Seattle', country: 'USA', remote: true }
},
{
id: '3',
email: 'tech@acme.com',
createdAt: new Date('2023-07-05'),
lastLogin: new Date('2023-10-23'),
isActive: true,
type: 'employer',
companyName: 'Acme Tech',
industry: 'Software',
companySize: '50-200',
location: { city: 'San Francisco', country: 'USA' }
},
{
id: '4',
email: 'careers@globex.com',
createdAt: new Date('2023-08-20'),
lastLogin: new Date('2023-10-20'),
isActive: false,
type: 'employer',
companyName: 'Globex Corporation',
industry: 'Manufacturing',
companySize: '1000+',
location: { city: 'Chicago', country: 'USA' }
}
];
// Component for User Management
const UserManagement: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [users, setUsers] = useState<User[]>(mockUsers);
const [openDialog, setOpenDialog] = useState(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [aiConfigOpen, setAiConfigOpen] = useState(false);
// Handle tab change
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
// Filter users based on tab value
const filteredUsers = users.filter(user => {
if (tabValue === 0) return true;
if (tabValue === 1) return user.type === 'candidate';
if (tabValue === 2) return user.type === 'employer';
return false;
});
// Handle open user detail dialog
const handleOpenUserDetails = (user: User) => {
setSelectedUser(user);
setOpenDialog(true);
};
// Handle close user detail dialog
const handleCloseDialog = () => {
setOpenDialog(false);
setSelectedUser(null);
};
// Handle open AI configuration dialog
const handleOpenAiConfig = (user: User) => {
setSelectedUser(user);
setAiConfigOpen(true);
};
// Handle close AI configuration dialog
const handleCloseAiConfig = () => {
setAiConfigOpen(false);
};
// Helper function to get user's name for display
const getUserDisplayName = (user: User) => {
if (user.type === 'candidate') {
return `${user.firstName} ${user.lastName}`;
} else {
return user.companyName;
}
};
// Helper function to format date
const formatDate = (date: Date) => {
return new Date(date).toLocaleDateString();
};
return (
<Box sx={{ width: '100%', p: 3 }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<Tabs
value={tabValue}
onChange={handleTabChange}
indicatorColor="primary"
textColor="primary"
centered
>
<Tab icon={<AssignmentInd />} label="All Users" />
<Tab icon={<Person />} label="Candidates" />
<Tab icon={<Business />} label="Employers" />
</Tabs>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>User</TableCell>
<TableCell>Type</TableCell>
{/* <TableCell>Location</TableCell> */}
{/* <TableCell>Created</TableCell> */}
<TableCell>Last Login</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredUsers.map((user) => (
<TableRow key={user.id} sx={{ "& > td": { whiteSpace: "nowrap"}}}>
<TableCell>
<Box sx={{ display: 'flex', alignItems: 'flex-start', flexDirection: "column" }}>
<Typography>{getUserDisplayName(user)}</Typography>
</Box>
</TableCell>
<TableCell>
<Chip
label={user.type === 'candidate' ? 'Candidate' : 'Employer'}
color={user.type === 'candidate' ? 'primary' : 'secondary'}
size="small"
/>
</TableCell>
{/* <TableCell>
{user.location.city}, {user.location.country}
{user.type === 'candidate' && user.location.remote &&
<Chip label="Remote" size="small" sx={{ ml: 1 }} />
}
</TableCell> */}
{/* <TableCell>{formatDate(user.createdAt)}</TableCell> */}
<TableCell>{formatDate(user.lastLogin)}</TableCell>
<TableCell>
<Chip
label={user.isActive ? 'Active' : 'Inactive'}
color={user.isActive ? 'success' : 'error'}
size="small"
/>
</TableCell>
<TableCell>
<Button
size="small"
variant="outlined"
onClick={() => handleOpenUserDetails(user)}
sx={{ mr: 1 }}
>
Details
</Button>
<Button
size="small"
variant="outlined"
color="secondary"
onClick={() => handleOpenAiConfig(user)}
>
AI Config
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
{/* User Details Dialog */}
<Dialog open={openDialog} onClose={handleCloseDialog} maxWidth="md" fullWidth>
{selectedUser && (
<>
<DialogTitle>
{selectedUser.type === 'candidate' ? 'Candidate Details' : 'Employer Details'}
</DialogTitle>
<DialogContent dividers>
{selectedUser.type === 'candidate' ? (
<Grid container spacing={2}>
<Grid size={{xs: 12, md: 6}}>
<Typography variant="subtitle1">Personal Information</Typography>
<TextField
label="First Name"
value={selectedUser.firstName}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
<TextField
label="Last Name"
value={selectedUser.lastName}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
<TextField
label="Email"
value={selectedUser.email}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<Typography variant="subtitle1">Skills</Typography>
<Box sx={{ mt: 2 }}>
{selectedUser.skills.map((skill) => (
<Chip
key={skill.id}
label={`${skill.name} (${skill.level})`}
sx={{ m: 0.5 }}
/>
))}
</Box>
</Grid>
</Grid>
) : (
<Grid container spacing={2}>
<Grid size={{xs: 12, md: 6}}>
<Typography variant="subtitle1">Company Information</Typography>
<TextField
label="Company Name"
value={selectedUser.companyName}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
<TextField
label="Industry"
value={selectedUser.industry}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
<TextField
label="Company Size"
value={selectedUser.companySize}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<Typography variant="subtitle1">Contact Information</Typography>
<TextField
label="Email"
value={selectedUser.email}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
<TextField
label="Location"
value={`${selectedUser.location.city}, ${selectedUser.location.country}`}
fullWidth
margin="normal"
InputProps={{ readOnly: true }}
/>
</Grid>
</Grid>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog}>Close</Button>
</DialogActions>
</>
)}
</Dialog>
{/* AI Config Dialog */}
<Dialog open={aiConfigOpen} onClose={handleCloseAiConfig} maxWidth="md" fullWidth>
{selectedUser && (
<>
<DialogTitle>
AI Configuration for {getUserDisplayName(selectedUser)}
</DialogTitle>
<DialogContent dividers>
<Typography variant="subtitle1" gutterBottom>
RAG Database Configuration
</Typography>
<FormControl fullWidth margin="normal">
<InputLabel id="embedding-model-label">Embedding Model</InputLabel>
<Select
labelId="embedding-model-label"
label="Embedding Model"
defaultValue="openai-ada-002"
>
<MenuItem value="openai-ada-002">OpenAI Ada 002</MenuItem>
<MenuItem value="bert-base">BERT Base</MenuItem>
<MenuItem value="sentence-t5">Sentence T5</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth margin="normal">
<InputLabel id="vector-store-label">Vector Store</InputLabel>
<Select
labelId="vector-store-label"
label="Vector Store"
defaultValue="pinecone"
>
<MenuItem value="pinecone">Pinecone</MenuItem>
<MenuItem value="qdrant">Qdrant</MenuItem>
<MenuItem value="faiss">FAISS</MenuItem>
</Select>
</FormControl>
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2 }}>
AI Model Parameters
</Typography>
<Grid container spacing={2}>
<Grid size={{xs: 12, md: 6}}>
<FormControl fullWidth margin="normal">
<InputLabel id="model-label">AI Model</InputLabel>
<Select
labelId="model-label"
label="AI Model"
defaultValue="gpt-4"
>
<MenuItem value="gpt-4">GPT-4</MenuItem>
<MenuItem value="claude-3">Claude 3</MenuItem>
<MenuItem value="custom">Custom</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<TextField
label="Temperature"
type="number"
defaultValue={0.7}
fullWidth
margin="normal"
InputProps={{ inputProps: { min: 0, max: 1, step: 0.1 } }}
/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<TextField
label="Max Tokens"
type="number"
defaultValue={2000}
fullWidth
margin="normal"
/>
</Grid>
<Grid size={{xs: 12, md: 6}}>
<TextField
label="Top P"
type="number"
defaultValue={0.95}
fullWidth
margin="normal"
InputProps={{ inputProps: { min: 0, max: 1, step: 0.05 } }}
/>
</Grid>
</Grid>
<TextField
label="System Prompt"
multiline
rows={4}
fullWidth
margin="normal"
defaultValue={`You are an AI assistant helping ${selectedUser.type === 'candidate' ? 'job candidates find relevant positions' : 'employers find qualified candidates'}. Be professional, helpful, and concise in your responses.`}
/>
<Typography variant="subtitle1" gutterBottom sx={{ mt: 2 }}>
Data Sources
</Typography>
<TableContainer component={Paper} sx={{ mt: 1 }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Source Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>Status</TableCell>
<TableCell>Last Refreshed</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Profile Data</TableCell>
<TableCell>Internal</TableCell>
<TableCell>
<Chip label="Active" color="success" size="small" />
</TableCell>
<TableCell>Auto</TableCell>
</TableRow>
<TableRow>
<TableCell>Company Documents</TableCell>
<TableCell>Document</TableCell>
<TableCell>
<Chip label="Active" color="success" size="small" />
</TableCell>
<TableCell>10/20/2024</TableCell>
</TableRow>
<TableRow>
<TableCell>Industry News</TableCell>
<TableCell>Web Crawler</TableCell>
<TableCell>
<Chip label="Active" color="success" size="small" />
</TableCell>
<TableCell>Daily</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseAiConfig}>Cancel</Button>
<Button variant="contained" color="primary">Save Configuration</Button>
</DialogActions>
</>
)}
</Dialog>
</Box>
);
};
export { UserManagement };