logs categories and hitcounts
This commit is contained in:
parent
9121f49a9b
commit
4ee508f01b
@ -6,6 +6,7 @@ import { ToastContainer } from "react-toastify";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { AuthProvider } from "./context/AuthContext";
|
||||
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function App() {
|
||||
|
||||
@ -9,3 +9,24 @@ export const getCategories = async () => {
|
||||
const res = await axiosInstance.get<CategoriesResponse>("/api/categories");
|
||||
return res.data.data;
|
||||
};
|
||||
|
||||
export const getCategoryById = async (id: number) => {
|
||||
const res = await axiosInstance.get<{data:CategoryType}>(`/api/categories/${id}`);
|
||||
return res.data.data;
|
||||
};
|
||||
|
||||
export const createCategory = async (name: string) => {
|
||||
const res = await axiosInstance.post(`/api/categories`, { name });
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const updateCategory = async (id: number, name: string) => {
|
||||
const res = await axiosInstance.put(`/api/categories/${id}`, { name });
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const deleteCategory = async (id: number) => {
|
||||
const res = await axiosInstance.delete(`/api/categories/${id}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
|
||||
24
src/api/logsApi.ts
Normal file
24
src/api/logsApi.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
|
||||
export type LogType = {
|
||||
id: number;
|
||||
description: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
interface LogsResponse {
|
||||
data: LogType[];
|
||||
meta: {
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const getLogs = async (page = 1) => {
|
||||
const res = await axiosInstance.get<LogsResponse>("/api/logs", {
|
||||
params: { page },
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
39
src/api/metricsService.ts
Normal file
39
src/api/metricsService.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import axiosInstance from "./axiosInstance";
|
||||
|
||||
export const hitApi = async (url: string): Promise<void> => {
|
||||
await axiosInstance.post("/api/hit", { url });
|
||||
};
|
||||
|
||||
export interface HitCountType {
|
||||
id: number;
|
||||
ip: string;
|
||||
device_type: string;
|
||||
user_agent: string;
|
||||
country: string | null;
|
||||
url: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface HitCountsResponse {
|
||||
data: HitCountType[];
|
||||
links: {
|
||||
first: string;
|
||||
last: string;
|
||||
prev: string | null;
|
||||
next: string | null;
|
||||
};
|
||||
meta: {
|
||||
current_page: number;
|
||||
from: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const getHitCounts = async (page = 1) => {
|
||||
const res = await axiosInstance.get<HitCountsResponse>("/api/hitcounts", {
|
||||
params: { page },
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
@ -13,6 +13,7 @@ import QuizIcon from "@mui/icons-material/Quiz";
|
||||
import CategoryIcon from "@mui/icons-material/Category";
|
||||
import ArticleIcon from "@mui/icons-material/Article";
|
||||
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
|
||||
import FmdBadIcon from '@mui/icons-material/FmdBad';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
@ -35,12 +36,17 @@ const menuItems = [
|
||||
{
|
||||
label: "User Tests",
|
||||
icon: <AssignmentIndIcon />,
|
||||
to: "/dashboard/userTests",
|
||||
to: "/dashboard/user-tests",
|
||||
},
|
||||
{
|
||||
label: "Categories",
|
||||
icon: <CategoryIcon />,
|
||||
to: "/dashboard/categories",
|
||||
},
|
||||
{
|
||||
label: "Hitcounts",
|
||||
icon: <FmdBadIcon />,
|
||||
to: "/dashboard/hitcounts",
|
||||
},
|
||||
{
|
||||
label: "Logs",
|
||||
|
||||
15
src/components/Cholecounter/Cholecounter.tsx
Normal file
15
src/components/Cholecounter/Cholecounter.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import { hitApi } from "../../api/metricsService";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const Cholecounter = () => {
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
hitApi(window.location.href);
|
||||
}, [location.pathname]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Cholecounter;
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from "@mui/material";
|
||||
import { StartTestWrapper } from "./StartTestForm.styles";
|
||||
import { useState } from "react";
|
||||
import { useCategories } from "../../hooks/Question/useCategories";
|
||||
import { useCategories } from "../../hooks/categories/useCategories";
|
||||
import type { Category } from "../shared/types/QuestionTypes";
|
||||
import { useStartTest } from "../../hooks/Tests/useStartTest";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
17
src/hooks/categories/useCreateCategory.ts
Normal file
17
src/hooks/categories/useCreateCategory.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createCategory } from "../../api/categoryApi";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useCreateCategory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => createCategory(name),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["categories"] });
|
||||
toast.success("Category Created");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
17
src/hooks/categories/useDeleteCategory.ts
Normal file
17
src/hooks/categories/useDeleteCategory.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { deleteCategory } from "../../api/categoryApi";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useDeleteCategory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: number) => deleteCategory(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["categories"] });
|
||||
toast.success("Category Deleted");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
10
src/hooks/categories/useGetCategoryById.ts
Normal file
10
src/hooks/categories/useGetCategoryById.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getCategoryById } from "../../api/categoryApi";
|
||||
|
||||
export const useCategoryById = (id?: number) => {
|
||||
return useQuery({
|
||||
queryKey: ["categories", id],
|
||||
queryFn: () => getCategoryById(id!),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
18
src/hooks/categories/useUpdateCategory.ts
Normal file
18
src/hooks/categories/useUpdateCategory.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { updateCategory } from "../../api/categoryApi";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useUpdateCategory = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, name }: { id: number; name: string }) =>
|
||||
updateCategory(id, name),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["categories"] });
|
||||
toast.success("Category Updated");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
9
src/hooks/hitcounts/useHitcounts.ts
Normal file
9
src/hooks/hitcounts/useHitcounts.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getHitCounts } from "../../api/metricsService";
|
||||
|
||||
export const useHitcounts = (page: number) => {
|
||||
return useQuery({
|
||||
queryKey: ["hitcounts", page],
|
||||
queryFn: () => getHitCounts(page),
|
||||
});
|
||||
};
|
||||
9
src/hooks/logsHook/useLogs.ts
Normal file
9
src/hooks/logsHook/useLogs.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getLogs } from "../../api/logsApi";
|
||||
|
||||
export const useLogs = (page: number) => {
|
||||
return useQuery({
|
||||
queryKey: ["logs", page],
|
||||
queryFn: () => getLogs(page),
|
||||
});
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { AdminSidebar } from "../../components/AdminSidebar/AdminSidebar";
|
||||
import { LayoutWrapper } from "../MainLayout/Layout.styles";
|
||||
import Header from "../../components/Header/Header";
|
||||
import { StyledDistance } from "../../components/shared/StyledDistance";
|
||||
import Cholecounter from "../../components/Cholecounter/Cholecounter";
|
||||
|
||||
const AdminLayout = () => {
|
||||
return (
|
||||
@ -10,6 +11,7 @@ const AdminLayout = () => {
|
||||
<Header />
|
||||
<StyledDistance />
|
||||
<AdminSidebar />
|
||||
<Cholecounter/>
|
||||
<Outlet />
|
||||
</LayoutWrapper>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@ import { Outlet } from "react-router-dom";
|
||||
import Header from "../../components/Header/Header";
|
||||
import { LayoutWrapper } from "./Layout.styles";
|
||||
import { StyledDistance } from "../../components/shared/StyledDistance";
|
||||
import Cholecounter from "../../components/Cholecounter/Cholecounter";
|
||||
|
||||
const MainLayout = () => {
|
||||
return (
|
||||
@ -9,6 +10,7 @@ const MainLayout = () => {
|
||||
<LayoutWrapper>
|
||||
<Header />
|
||||
<StyledDistance />
|
||||
<Cholecounter/>
|
||||
<Outlet />
|
||||
</LayoutWrapper>
|
||||
</>
|
||||
|
||||
136
src/pages/CategoriesPage/CategoriesPage.tsx
Normal file
136
src/pages/CategoriesPage/CategoriesPage.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Container from "../../components/shared/Container";
|
||||
import { useCategories } from "../../hooks/categories/useCategories";
|
||||
import { useDeleteCategory } from "../../hooks/categories/useDeleteCategory";
|
||||
|
||||
const CategoriesPage = () => {
|
||||
const [deleteCategoryId, setDeleteCategoryId] = useState<number | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useCategories();
|
||||
const deleteMutation = useDeleteCategory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCreateCategory = () => {
|
||||
navigate("/dashboard/categories/create");
|
||||
};
|
||||
|
||||
const handleUpdateCategory = (id: number) => {
|
||||
navigate(`/dashboard/categories/${id}/update`);
|
||||
};
|
||||
|
||||
const handleDeleteCategory = () => {
|
||||
if (deleteCategoryId !== null) {
|
||||
deleteMutation.mutate(deleteCategoryId, {
|
||||
onSuccess: () => setDeleteCategoryId(null),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
if (isError)
|
||||
return <Typography color="error">Error loading categories.</Typography>;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
||||
<Typography variant="h4">Categories</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleCreateCategory}
|
||||
>
|
||||
Create Category
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Questions</TableCell>
|
||||
<TableCell>User Tests</TableCell>
|
||||
<TableCell>Created At</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{data?.map((category) => (
|
||||
<TableRow key={category.id}>
|
||||
<TableCell>{category.id}</TableCell>
|
||||
<TableCell>{category.name}</TableCell>
|
||||
<TableCell>{category.questions_count ?? "-"}</TableCell>
|
||||
<TableCell>{category.user_tests_count ?? "-"}</TableCell>
|
||||
<TableCell>
|
||||
{category.created_at
|
||||
? new Date(category.created_at).toLocaleDateString()
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ mr: 1 }}
|
||||
onClick={() => handleUpdateCategory(category.id)}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
onClick={() => setDeleteCategoryId(category.id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Dialog
|
||||
open={deleteCategoryId !== null}
|
||||
onClose={() => setDeleteCategoryId(null)}
|
||||
>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this category?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteCategoryId(null)}>Cancel</Button>
|
||||
<Button
|
||||
color="error"
|
||||
onClick={handleDeleteCategory}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoriesPage;
|
||||
77
src/pages/CategoryForm/CategoryForm.tsx
Normal file
77
src/pages/CategoryForm/CategoryForm.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useCreateCategory } from "../../hooks/categories/useCreateCategory";
|
||||
import { useUpdateCategory } from "../../hooks/categories/useUpdateCategory";
|
||||
import { useCategoryById } from "../../hooks/categories/useGetCategoryById";
|
||||
|
||||
const CategoryForm = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isUpdate = Boolean(id);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const { data: category, isLoading } = useCategoryById(
|
||||
id ? Number(id) : undefined
|
||||
);
|
||||
|
||||
const createCategoryMutation = useCreateCategory();
|
||||
const updateCategoryMutation = useUpdateCategory();
|
||||
|
||||
useEffect(() => {
|
||||
if (category) {
|
||||
setName(category.name);
|
||||
}
|
||||
}, [category]);
|
||||
|
||||
if (isUpdate && isLoading) return <CircularProgress />;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isUpdate && id) {
|
||||
updateCategoryMutation.mutate(
|
||||
{ id: Number(id), name },
|
||||
{ onSuccess: () => navigate("/dashboard/categories") }
|
||||
);
|
||||
} else {
|
||||
createCategoryMutation.mutate(name, {
|
||||
onSuccess: () => navigate("/dashboard/categories"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: 500, mx: "auto", mt: 5 }}>
|
||||
<Typography variant="h5" mb={3}>
|
||||
{isUpdate ? "Update Category" : "Create Category"}
|
||||
</Typography>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<TextField
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
fullWidth
|
||||
required
|
||||
sx={{ mb: 3 }}
|
||||
/>
|
||||
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
{isUpdate ? "Update" : "Create"}
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryForm;
|
||||
|
||||
71
src/pages/HitcountsPage/HitcountsPage.tsx
Normal file
71
src/pages/HitcountsPage/HitcountsPage.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Pagination,
|
||||
} from "@mui/material";
|
||||
import Container from "../../components/shared/Container";
|
||||
import { useHitcounts } from "../../hooks/hitcounts/useHitcounts";
|
||||
|
||||
const HitCountsPage = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, isLoading, isError } = useHitcounts(currentPage);
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
if (isError)
|
||||
return <Typography color="error">Error loading hitcounts.</Typography>;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4">Hit Counts</Typography>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>IP</TableCell>
|
||||
<TableCell>Device</TableCell>
|
||||
<TableCell>User Agent</TableCell>
|
||||
<TableCell>Country</TableCell>
|
||||
<TableCell>URL</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data?.data.map((hit) => (
|
||||
<TableRow key={hit.id}>
|
||||
<TableCell>{hit.id}</TableCell>
|
||||
<TableCell>{hit.ip}</TableCell>
|
||||
<TableCell>{hit.device_type}</TableCell>
|
||||
<TableCell>{hit.user_agent}</TableCell>
|
||||
<TableCell>{hit.country || "-"}</TableCell>
|
||||
<TableCell>{hit.url}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Pagination
|
||||
color="primary"
|
||||
shape="rounded"
|
||||
count={data?.meta.last_page || 1}
|
||||
page={currentPage}
|
||||
onChange={(_, value) => setCurrentPage(value)}
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "center" }}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default HitCountsPage;
|
||||
@ -15,7 +15,7 @@ import Question from "../../components/Question/Question";
|
||||
import { useQuestions } from "../../hooks/Question/useQuestions";
|
||||
import type { QuestionType } from "../../components/shared/types/QuestionTypes";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import { useCategories } from "../../hooks/Question/useCategories";
|
||||
import { useCategories } from "../../hooks/categories/useCategories";
|
||||
|
||||
const IndexPage = () => {
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
67
src/pages/LogsPage/LogsPage.tsx
Normal file
67
src/pages/LogsPage/LogsPage.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Pagination,
|
||||
} from "@mui/material";
|
||||
import Container from "../../components/shared/Container";
|
||||
import { useLogs } from "../../hooks/logsHook/useLogs";
|
||||
|
||||
const LogsPage = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, isLoading, isError } = useLogs(currentPage);
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
if (isError)
|
||||
return <Typography color="error">Error loading logs.</Typography>;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h4" mb={3}>
|
||||
Activity Logs
|
||||
</Typography>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Created At</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{data?.data.map((log) => (
|
||||
<TableRow key={log.id}>
|
||||
<TableCell>{log.id}</TableCell>
|
||||
<TableCell>{log.description}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(log.created_at).toLocaleString()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Pagination
|
||||
color="primary"
|
||||
shape="rounded"
|
||||
count={data?.meta.last_page}
|
||||
page={currentPage}
|
||||
onChange={(_, value) => setCurrentPage(value)}
|
||||
sx={{ mt: 3, display: "flex", justifyContent: "center" }}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogsPage;
|
||||
@ -14,7 +14,7 @@ import {
|
||||
Typography,
|
||||
type SelectChangeEvent,
|
||||
} from "@mui/material";
|
||||
import { useCategories } from "../../hooks/Question/useCategories";
|
||||
import { useCategories } from "../../hooks/categories/useCategories";
|
||||
|
||||
const TestsPage = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
@ -16,6 +16,10 @@ import AdminLayout from "../layouts/AdminLayout/AdminLayout";
|
||||
import Container from "../components/shared/Container";
|
||||
import UsersPage from "../pages/UsersPage/UsersPage";
|
||||
import UserForm from "../pages/UserForm/UserForm";
|
||||
import CategoriesPage from "../pages/CategoriesPage/CategoriesPage";
|
||||
import CategoryForm from "../pages/CategoryForm/CategoryForm";
|
||||
import LogsPage from "../pages/LogsPage/LogsPage";
|
||||
import HitCountsPage from "../pages/HitcountsPage/HitcountsPage";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -92,8 +96,16 @@ const router = createBrowserRouter([
|
||||
{ path: "questions", element: <Container>questions</Container> },
|
||||
{ path: "tests", element: <Container>tests</Container> },
|
||||
{ path: "user-tests", element: <Container>User Tests</Container> },
|
||||
{ path: "categories", element: <Container>categories</Container> },
|
||||
{ path: "logs", element: <Container>logs</Container> },
|
||||
{
|
||||
path: "categories",
|
||||
children: [
|
||||
{ index: true, element: <CategoriesPage /> },
|
||||
{ path: "create", element: <CategoryForm /> },
|
||||
{ path: ":id/update", element: <CategoryForm /> },
|
||||
],
|
||||
},
|
||||
{ path: "logs", element: <LogsPage /> },
|
||||
{path: "hitcounts", element: <HitCountsPage/>}
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user