582 lines
19 KiB
TypeScript
582 lines
19 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Grid,
|
|
Paper,
|
|
Typography,
|
|
Box,
|
|
Card,
|
|
CardContent,
|
|
Button,
|
|
TextField,
|
|
InputAdornment,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
Chip,
|
|
IconButton,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
List,
|
|
ListItem,
|
|
ListItemText,
|
|
Rating,
|
|
Tabs,
|
|
Tab,
|
|
Alert,
|
|
Fab,
|
|
Tooltip,
|
|
} from '@mui/material';
|
|
import {
|
|
Search,
|
|
Add,
|
|
Edit,
|
|
Visibility,
|
|
Star,
|
|
TrendingUp,
|
|
CheckCircle,
|
|
Warning,
|
|
Psychology,
|
|
FilterList,
|
|
Sort,
|
|
} from '@mui/icons-material';
|
|
import { DataGrid } from '@mui/x-data-grid';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { AnimatedCard } from '../../components/Animated/AnimatedCard';
|
|
import { GlassmorphismCard } from '../../components/Animated/GlassmorphismCard';
|
|
|
|
const knowledgeArticles = [
|
|
{
|
|
id: 1,
|
|
title: 'How to Reset Your Password',
|
|
category: 'User Support',
|
|
tags: ['password', 'authentication', 'user'],
|
|
content: 'Step-by-step guide to reset your password using the self-service portal...',
|
|
author: 'John Smith',
|
|
created: '2024-01-10',
|
|
updated: '2024-01-15',
|
|
version: '2.1',
|
|
status: 'published',
|
|
rating: 4.5,
|
|
views: 1250,
|
|
helpful: 89,
|
|
aiSuggestions: 3,
|
|
lastReviewed: '2024-01-14'
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Troubleshooting Email Issues',
|
|
category: 'Infrastructure',
|
|
tags: ['email', 'outlook', 'connectivity'],
|
|
content: 'Common email problems and their solutions including connectivity issues...',
|
|
author: 'Sarah Johnson',
|
|
created: '2024-01-08',
|
|
updated: '2024-01-12',
|
|
version: '1.8',
|
|
status: 'published',
|
|
rating: 4.2,
|
|
views: 890,
|
|
helpful: 67,
|
|
aiSuggestions: 2,
|
|
lastReviewed: '2024-01-10'
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Setting Up VPN Connection',
|
|
category: 'Network',
|
|
tags: ['vpn', 'network', 'remote'],
|
|
content: 'Complete guide to setting up and using VPN for remote access...',
|
|
author: 'Mike Davis',
|
|
created: '2024-01-12',
|
|
updated: '2024-01-14',
|
|
version: '3.0',
|
|
status: 'published',
|
|
rating: 4.7,
|
|
views: 2100,
|
|
helpful: 156,
|
|
aiSuggestions: 1,
|
|
lastReviewed: '2024-01-13'
|
|
}
|
|
];
|
|
|
|
const categories = ['User Support', 'Infrastructure', 'Network', 'Hardware', 'Applications', 'Security', 'General'];
|
|
|
|
const KnowledgeArticles: React.FC = () => {
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [categoryFilter, setCategoryFilter] = useState('All');
|
|
const [statusFilter, setStatusFilter] = useState('All');
|
|
const [selectedArticle, setSelectedArticle] = useState<any>(null);
|
|
const [articleDialogOpen, setArticleDialogOpen] = useState<boolean>(false);
|
|
const [tabValue, setTabValue] = useState<number>(0);
|
|
const [newArticle, setNewArticle] = useState({
|
|
title: '',
|
|
category: '',
|
|
tags: [],
|
|
content: '',
|
|
status: 'draft'
|
|
});
|
|
|
|
const columns = [
|
|
{ field: 'id', headerName: 'ID', width: 80 },
|
|
{ field: 'title', headerName: 'Title', width: 300 },
|
|
{ field: 'category', headerName: 'Category', width: 120 },
|
|
{ field: 'author', headerName: 'Author', width: 150 },
|
|
{ field: 'version', headerName: 'Version', width: 100 },
|
|
{
|
|
field: 'status',
|
|
headerName: 'Status',
|
|
width: 100,
|
|
renderCell: (params: any) => (
|
|
<Chip
|
|
label={params.value}
|
|
size="small"
|
|
color={params.value === 'published' ? 'success' :
|
|
params.value === 'draft' ? 'warning' : 'info'}
|
|
/>
|
|
),
|
|
},
|
|
{ field: 'rating', headerName: 'Rating', width: 100 },
|
|
{ field: 'views', headerName: 'Views', width: 100 },
|
|
{ field: 'updated', headerName: 'Updated', width: 120 },
|
|
{
|
|
field: 'actions',
|
|
headerName: 'Actions',
|
|
width: 150,
|
|
renderCell: (params: any) => (
|
|
<Box>
|
|
<IconButton size="small" onClick={() => handleViewArticle(params.row)}>
|
|
<Visibility />
|
|
</IconButton>
|
|
<IconButton size="small" onClick={() => handleEditArticle(params.row)}>
|
|
<Edit />
|
|
</IconButton>
|
|
</Box>
|
|
),
|
|
},
|
|
];
|
|
|
|
const handleViewArticle = (article: any) => {
|
|
setSelectedArticle(article);
|
|
setArticleDialogOpen(true);
|
|
};
|
|
|
|
const handleEditArticle = (article: any) => {
|
|
setSelectedArticle(article);
|
|
setNewArticle(article);
|
|
setArticleDialogOpen(true);
|
|
};
|
|
|
|
const handleCreateArticle = () => {
|
|
setSelectedArticle(null);
|
|
setNewArticle({
|
|
title: '',
|
|
category: '',
|
|
tags: [],
|
|
content: '',
|
|
status: 'draft'
|
|
});
|
|
setArticleDialogOpen(true);
|
|
};
|
|
|
|
const handleSaveArticle = () => {
|
|
console.log('Saving article:', newArticle);
|
|
setArticleDialogOpen(false);
|
|
};
|
|
|
|
const filteredArticles = knowledgeArticles.filter(article => {
|
|
const matchesSearch = article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
article.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
article.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
const matchesCategory = categoryFilter === 'All' || article.category === categoryFilter;
|
|
const matchesStatus = statusFilter === 'All' || article.status === statusFilter;
|
|
return matchesSearch && matchesCategory && matchesStatus;
|
|
});
|
|
|
|
return (
|
|
<Box>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
|
<Box>
|
|
<Typography variant="h4" gutterBottom sx={{ fontWeight: 700 }}>
|
|
Knowledge Articles
|
|
</Typography>
|
|
<Typography variant="subtitle1" color="text.secondary">
|
|
Manage and organize your knowledge base with AI-powered insights
|
|
</Typography>
|
|
</Box>
|
|
<motion.div
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<Add />}
|
|
onClick={handleCreateArticle}
|
|
sx={{
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
borderRadius: 2,
|
|
px: 3,
|
|
py: 1.5,
|
|
fontWeight: 600,
|
|
'&:hover': {
|
|
transform: 'translateY(-2px)',
|
|
boxShadow: '0 8px 25px rgba(102, 126, 234, 0.4)',
|
|
},
|
|
transition: 'all 0.2s ease-in-out',
|
|
}}
|
|
>
|
|
Create Article
|
|
</Button>
|
|
</motion.div>
|
|
</Box>
|
|
</motion.div>
|
|
|
|
<AnimatedCard delay={0.2}>
|
|
<GlassmorphismCard sx={{ p: 2, mb: 3 }}>
|
|
<Tabs
|
|
value={tabValue}
|
|
onChange={(e, newValue) => setTabValue(newValue)}
|
|
sx={{
|
|
'& .MuiTab-root': {
|
|
fontWeight: 600,
|
|
textTransform: 'none',
|
|
minHeight: 48,
|
|
},
|
|
'& .Mui-selected': {
|
|
color: 'primary.main',
|
|
},
|
|
}}
|
|
>
|
|
<Tab label="All Articles" />
|
|
<Tab label="AI Suggestions" />
|
|
<Tab label="Analytics" />
|
|
</Tabs>
|
|
</GlassmorphismCard>
|
|
</AnimatedCard>
|
|
|
|
<AnimatePresence mode="wait">
|
|
{tabValue === 0 && (
|
|
<motion.div
|
|
key="articles"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
{/* Filters */}
|
|
<AnimatedCard delay={0.3}>
|
|
<GlassmorphismCard sx={{ p: 3, mb: 3 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
|
<FilterList sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
|
Filters & Search
|
|
</Typography>
|
|
</Box>
|
|
<Grid container spacing={2} alignItems="center">
|
|
<Grid item xs={12} md={4}>
|
|
<TextField
|
|
fullWidth
|
|
placeholder="Search articles..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
InputProps={{
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Search />
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
sx={{
|
|
'& .MuiOutlinedInput-root': {
|
|
borderRadius: 2,
|
|
}
|
|
}}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={3}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Category</InputLabel>
|
|
<Select
|
|
value={categoryFilter}
|
|
label="Category"
|
|
onChange={(e) => setCategoryFilter(e.target.value)}
|
|
sx={{ borderRadius: 2 }}
|
|
>
|
|
<MenuItem value="All">All Categories</MenuItem>
|
|
{categories.map((category) => (
|
|
<MenuItem key={category} value={category}>{category}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12} md={3}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Status</InputLabel>
|
|
<Select
|
|
value={statusFilter}
|
|
label="Status"
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
sx={{ borderRadius: 2 }}
|
|
>
|
|
<MenuItem value="All">All Status</MenuItem>
|
|
<MenuItem value="published">Published</MenuItem>
|
|
<MenuItem value="draft">Draft</MenuItem>
|
|
<MenuItem value="review">Under Review</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12} md={2}>
|
|
<Button
|
|
variant="outlined"
|
|
startIcon={<Sort />}
|
|
fullWidth
|
|
sx={{ borderRadius: 2, py: 1.5 }}
|
|
>
|
|
Sort
|
|
</Button>
|
|
</Grid>
|
|
</Grid>
|
|
</GlassmorphismCard>
|
|
</AnimatedCard>
|
|
|
|
{/* Articles Table */}
|
|
<AnimatedCard delay={0.4}>
|
|
<GlassmorphismCard sx={{ p: 2 }}>
|
|
<DataGrid
|
|
rows={filteredArticles}
|
|
columns={columns}
|
|
pageSize={10}
|
|
rowsPerPageOptions={[10, 25, 50]}
|
|
checkboxSelection
|
|
disableSelectionOnClick
|
|
sx={{
|
|
height: 500,
|
|
border: 'none',
|
|
'& .MuiDataGrid-cell': {
|
|
borderBottom: '1px solid rgba(0,0,0,0.1)',
|
|
},
|
|
'& .MuiDataGrid-columnHeaders': {
|
|
backgroundColor: 'rgba(0,0,0,0.02)',
|
|
borderBottom: '2px solid rgba(0,0,0,0.1)',
|
|
},
|
|
}}
|
|
/>
|
|
</GlassmorphismCard>
|
|
</AnimatedCard>
|
|
</motion.div>
|
|
)}
|
|
|
|
{tabValue === 1 && (
|
|
<Grid container spacing={3}>
|
|
{knowledgeArticles.map((article: any) => (
|
|
<Grid item xs={12} md={6} key={article.id}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
|
<Typography variant="h6">{article.title}</Typography>
|
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
|
<Chip
|
|
label={`${article.aiSuggestions} suggestions`}
|
|
size="small"
|
|
color="primary"
|
|
icon={<Psychology />}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
{article.content.substring(0, 100)}...
|
|
</Typography>
|
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
|
|
{article.tags.map((tag: string, index: number) => (
|
|
<Chip key={index} label={tag} size="small" variant="outlined" />
|
|
))}
|
|
</Box>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<Rating value={article.rating} readOnly size="small" />
|
|
<Typography variant="body2" color="text.secondary">
|
|
({article.rating})
|
|
</Typography>
|
|
</Box>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{article.views} views
|
|
</Typography>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
)}
|
|
|
|
{tabValue === 2 && (
|
|
|
|
<Grid container spacing={3}>
|
|
<Grid item xs={12} md={6}>
|
|
<Paper sx={{ p: 2 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
Article Performance
|
|
</Typography>
|
|
<List>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="Most Viewed Article"
|
|
secondary="Setting Up VPN Connection (2,100 views)"
|
|
/>
|
|
<TrendingUp color="success" />
|
|
</ListItem>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="Highest Rated"
|
|
secondary="VPN Setup Guide (4.7/5.0)"
|
|
/>
|
|
<Star color="warning" />
|
|
</ListItem>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="Most Helpful"
|
|
secondary="Password Reset Guide (89 helpful votes)"
|
|
/>
|
|
<CheckCircle color="success" />
|
|
</ListItem>
|
|
</List>
|
|
</Paper>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<Paper sx={{ p: 2 }}>
|
|
<Typography variant="h6" gutterBottom>
|
|
AI Insights
|
|
</Typography>
|
|
<Alert severity="info" sx={{ mb: 2 }}>
|
|
<Typography variant="subtitle2">Knowledge Base Health</Typography>
|
|
<Typography variant="body2">
|
|
Your knowledge base is performing well with 4.3 average rating and 95% user satisfaction.
|
|
</Typography>
|
|
</Alert>
|
|
<List>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="Articles Needing Updates"
|
|
secondary="3 articles flagged for review"
|
|
/>
|
|
<Warning color="warning" />
|
|
</ListItem>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="AI Suggestions Generated"
|
|
secondary="15 suggestions this week"
|
|
/>
|
|
<Psychology color="primary" />
|
|
</ListItem>
|
|
<ListItem>
|
|
<ListItemText
|
|
primary="Search Success Rate"
|
|
secondary="87% of searches find relevant articles"
|
|
/>
|
|
<CheckCircle color="success" />
|
|
</ListItem>
|
|
</List>
|
|
</Paper>
|
|
</Grid>
|
|
</Grid>
|
|
)}
|
|
</AnimatePresence>
|
|
|
|
{/* Article Dialog */}
|
|
<Dialog open={articleDialogOpen} onClose={() => setArticleDialogOpen(false)} maxWidth="md" fullWidth>
|
|
<DialogTitle>
|
|
{selectedArticle ? 'Edit Article' : 'Create New Article'}
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<Grid container spacing={2} sx={{ mt: 1 }}>
|
|
<Grid item xs={12}>
|
|
<TextField
|
|
fullWidth
|
|
label="Article Title"
|
|
value={newArticle.title}
|
|
onChange={(e) => setNewArticle(prev => ({ ...prev, title: e.target.value }))}
|
|
required
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<FormControl fullWidth required>
|
|
<InputLabel>Category</InputLabel>
|
|
<Select
|
|
value={newArticle.category}
|
|
label="Category"
|
|
onChange={(e) => setNewArticle(prev => ({ ...prev, category: e.target.value }))}
|
|
>
|
|
{categories.map((category) => (
|
|
<MenuItem key={category} value={category}>{category}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Status</InputLabel>
|
|
<Select
|
|
value={newArticle.status}
|
|
label="Status"
|
|
onChange={(e) => setNewArticle(prev => ({ ...prev, status: e.target.value }))}
|
|
>
|
|
<MenuItem value="draft">Draft</MenuItem>
|
|
<MenuItem value="review">Under Review</MenuItem>
|
|
<MenuItem value="published">Published</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12}>
|
|
<TextField
|
|
fullWidth
|
|
multiline
|
|
rows={8}
|
|
label="Content"
|
|
value={newArticle.content}
|
|
onChange={(e) => setNewArticle(prev => ({ ...prev, content: e.target.value }))}
|
|
placeholder="Write your article content here..."
|
|
required
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setArticleDialogOpen(false)}>Cancel</Button>
|
|
<Button variant="contained" onClick={handleSaveArticle}>
|
|
{selectedArticle ? 'Update' : 'Create'}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{/* Floating Action Button */}
|
|
<Tooltip title="Create New Article" placement="left">
|
|
<Fab
|
|
color="primary"
|
|
aria-label="add"
|
|
onClick={handleCreateArticle}
|
|
sx={{
|
|
position: 'fixed',
|
|
bottom: 24,
|
|
right: 24,
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
'&:hover': {
|
|
transform: 'scale(1.1)',
|
|
boxShadow: '0 8px 25px rgba(102, 126, 234, 0.4)',
|
|
},
|
|
transition: 'all 0.2s ease-in-out',
|
|
}}
|
|
>
|
|
<Add />
|
|
</Fab>
|
|
</Tooltip>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default KnowledgeArticles; |