508 lines
17 KiB
TypeScript
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 }; |