admin usertest results
This commit is contained in:
parent
4ee508f01b
commit
b7313488a4
@ -7,6 +7,30 @@ import {
|
||||
} from "../components/shared/types/TestTypes";
|
||||
import axiosInstance from "./axiosInstance";
|
||||
|
||||
export type UserTestsResponse = {
|
||||
data: UserTestType[];
|
||||
links: {
|
||||
first: string;
|
||||
last: string;
|
||||
prev: string | null;
|
||||
next: string | null;
|
||||
};
|
||||
meta: {
|
||||
current_page: number;
|
||||
from: number | null;
|
||||
last_page: number;
|
||||
path: string;
|
||||
per_page: number;
|
||||
to: number | null;
|
||||
total: number;
|
||||
links: {
|
||||
url: string | null;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export const startTest = async (data: StartTestPayload) => {
|
||||
const res = await axiosInstance.post<{ data: UserTestType }>(
|
||||
"/api/user-tests",
|
||||
@ -64,3 +88,19 @@ export const getTestById = async (id: number) => {
|
||||
const res = await axiosInstance.get<{ data: TestType }>(`/api/tests/${id}`);
|
||||
return res.data.data;
|
||||
};
|
||||
|
||||
export const getAllUserTests = async (
|
||||
page = 1,
|
||||
test_id?: number,
|
||||
question_id?: number
|
||||
) => {
|
||||
const res = await axiosInstance.get<UserTestsResponse>("/api/user-tests", {
|
||||
params: { page, test_id, question_id },
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const deleteUserTest = async (id: number) => {
|
||||
const res = await axiosInstance.delete(`/api/user-tests/${id}`);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -75,9 +75,9 @@ const LearningAnswers = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ mt: 2 }}
|
||||
onClick={() => setShowAnswer(true)}
|
||||
onClick={() => setShowAnswer(!showAnswer)}
|
||||
>
|
||||
Reveal Answer
|
||||
{showAnswer ? "Hide Answer" : "Reveal Answer"}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@ -11,6 +11,7 @@ interface TestCardProps {
|
||||
const TestCard = ({ test }: TestCardProps) => {
|
||||
let title: string | undefined = "User Test";
|
||||
|
||||
|
||||
if (test.test_id) {
|
||||
const { data } = useGetTestById(test.test_id);
|
||||
title = data?.title;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Box, Button, Chip, Typography } from "@mui/material";
|
||||
import { Box, Button, Chip, Tooltip, Typography } from "@mui/material";
|
||||
import CategoryIcon from "@mui/icons-material/Category";
|
||||
import type { TestType } from "../shared/types/TestTypes";
|
||||
import { useStartTestById } from "../../hooks/Tests/useStartTestById";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface TestCardProps {
|
||||
test: TestType;
|
||||
@ -9,6 +10,8 @@ interface TestCardProps {
|
||||
|
||||
const TestListCard = ({ test }: TestCardProps) => {
|
||||
const { mutate: startTest } = useStartTestById();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const title = test.title;
|
||||
|
||||
let statusLabel = "";
|
||||
@ -33,21 +36,36 @@ const TestListCard = ({ test }: TestCardProps) => {
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
flexDirection: {
|
||||
xs: "column",
|
||||
sm: "column",
|
||||
md: "row"
|
||||
xs: "column",
|
||||
sm: "column",
|
||||
md: "row",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box sx={{display: "flex", gap: 1, flexDirection: {xs: "column", sm: "row"}}}>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
<Chip
|
||||
icon={<CategoryIcon />}
|
||||
label={test.category.name}
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: 1,
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
}}
|
||||
>
|
||||
<Tooltip title="View test details" placement="top" arrow>
|
||||
<Typography
|
||||
variant="h6"
|
||||
color="primary"
|
||||
sx={{ cursor: "pointer", color: "#000" }}
|
||||
onClick={() => navigate(`/tests/view/${test.id}`)}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Chip
|
||||
icon={<CategoryIcon />}
|
||||
label={test.category.name}
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{test.description && (
|
||||
@ -67,20 +85,17 @@ const TestListCard = ({ test }: TestCardProps) => {
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mt: 1 }}>
|
||||
|
||||
<Chip label={statusLabel} color={statusColor} />
|
||||
{test.is_available && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => startTest(test.id)}
|
||||
>
|
||||
Start Test
|
||||
</Button>
|
||||
)}
|
||||
{test.is_available && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => startTest(test.id)}
|
||||
>
|
||||
Start Test
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
69
src/components/UserTestRow/UserTestRow.tsx
Normal file
69
src/components/UserTestRow/UserTestRow.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { TableRow, TableCell, Button } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { UserTestType } from "../../components/shared/types/TestTypes";
|
||||
import { useGetTestById } from "../../hooks/Tests/useGetTestById";
|
||||
import { formatDate } from "../../utils/functions";
|
||||
|
||||
type Props = {
|
||||
userTest: UserTestType;
|
||||
onDelete: (id: number) => void;
|
||||
};
|
||||
|
||||
const UserTestRow = ({ userTest, onDelete }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const testId = userTest.test_id;
|
||||
let title = "User Test";
|
||||
let author = "—";
|
||||
|
||||
if (testId) {
|
||||
const { data } = useGetTestById(testId);
|
||||
title = data?.title ?? "User Test";
|
||||
author = data?.author.username ?? "-";
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>{userTest.id}</TableCell>
|
||||
|
||||
<TableCell>{title}</TableCell>
|
||||
|
||||
<TableCell>{userTest.user?.username ?? "Unknown"}</TableCell>
|
||||
|
||||
<TableCell>{author}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{userTest.score !== undefined ? userTest.score : "—"}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{userTest.is_completed ? "Yes" : "No"}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{formatDate(userTest.created_at)}
|
||||
</TableCell>
|
||||
<TableCell>{formatDate(userTest.closed_at)}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ mr: 1 }}
|
||||
onClick={() => navigate(`/tests/${userTest.id}`)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
variant="outlined"
|
||||
onClick={() => onDelete(userTest.id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTestRow;
|
||||
15
src/hooks/Tests/useDeteleUserTest.ts
Normal file
15
src/hooks/Tests/useDeteleUserTest.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { deleteUserTest } from "../../api/testApi";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useDeleteUserTest = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: number) => deleteUserTest(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["all-user-tests"] });
|
||||
toast.success("User Test deleted")
|
||||
},
|
||||
});
|
||||
};
|
||||
12
src/hooks/Tests/useGetAllUserTests.ts
Normal file
12
src/hooks/Tests/useGetAllUserTests.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getAllUserTests } from "../../api/testApi";
|
||||
|
||||
export const useGetAllUserTests = (
|
||||
page: number,
|
||||
test_id?: number,
|
||||
question_id?: number
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: ["all-user-tests", page],
|
||||
queryFn: () => getAllUserTests(page, test_id, question_id),
|
||||
});
|
||||
114
src/pages/AdminUserTestsPage/AdminUserTestsPage.tsx
Normal file
114
src/pages/AdminUserTestsPage/AdminUserTestsPage.tsx
Normal file
@ -0,0 +1,114 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Pagination,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from "@mui/material";
|
||||
|
||||
import Container from "../../components/shared/Container";
|
||||
import { useGetAllUserTests } from "../../hooks/Tests/useGetAllUserTests";
|
||||
import { useDeleteUserTest } from "../../hooks/Tests/useDeteleUserTest";
|
||||
|
||||
import UserTestRow from "../../components/UserTestRow/UserTestRow";
|
||||
|
||||
const UserTestsPage = () => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useGetAllUserTests(currentPage);
|
||||
const deleteMutation = useDeleteUserTest();
|
||||
|
||||
const handleDelete = () => {
|
||||
if (deleteId !== null) {
|
||||
deleteMutation.mutate(deleteId, {
|
||||
onSuccess: () => setDeleteId(null),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) return <CircularProgress />;
|
||||
if (isError)
|
||||
return <Typography color="error">Error loading user tests.</Typography>;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
||||
<Typography variant="h4">User Tests</Typography>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>Test</TableCell>
|
||||
<TableCell>Test Taker</TableCell>
|
||||
<TableCell>Test Author</TableCell>
|
||||
<TableCell>Score</TableCell>
|
||||
<TableCell>Completed</TableCell>
|
||||
<TableCell>Created At</TableCell>
|
||||
<TableCell>Closed At</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{data?.data.map((ut) => (
|
||||
<UserTestRow
|
||||
key={ut.id}
|
||||
userTest={ut}
|
||||
onDelete={(id) => setDeleteId(id)}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Pagination
|
||||
color="primary"
|
||||
shape="rounded"
|
||||
count={data?.meta.last_page}
|
||||
page={currentPage}
|
||||
onChange={(_, value) => setCurrentPage(value)}
|
||||
sx={{
|
||||
mt: 3,
|
||||
mb: 3,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
|
||||
<Dialog open={deleteId !== null} onClose={() => setDeleteId(null)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this user test?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteId(null)}>Cancel</Button>
|
||||
<Button
|
||||
color="error"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTestsPage;
|
||||
147
src/pages/SingleTestPage/SingleTestPage.tsx
Normal file
147
src/pages/SingleTestPage/SingleTestPage.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetTestById } from "../../hooks/Tests/useGetTestById";
|
||||
import Container from "../../components/shared/Container";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Pagination,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import LearningAnswers from "../../components/Answers/Answers";
|
||||
import NotFoundPage from "../NotFoundPage/NotFoundPage";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import { useState } from "react";
|
||||
import { useGetAllUserTests } from "../../hooks/Tests/useGetAllUserTests";
|
||||
import UserTestRow from "../../components/UserTestRow/UserTestRow";
|
||||
import { useDeleteUserTest } from "../../hooks/Tests/useDeteleUserTest";
|
||||
|
||||
export const SingleTestPage = () => {
|
||||
const { id } = useParams();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||
const deleteMutation = useDeleteUserTest();
|
||||
const { data: tests } = useGetAllUserTests(currentPage, Number(id));
|
||||
|
||||
const handleDelete = () => {
|
||||
if (deleteId !== null) {
|
||||
deleteMutation.mutate(deleteId, {
|
||||
onSuccess: () => setDeleteId(null),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { data: test } = useGetTestById(Number(id));
|
||||
if (!test) {
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h2" sx={{ mb: 3 }}>
|
||||
{test?.title}
|
||||
</Typography>
|
||||
|
||||
{test?.questions?.map((q) => (
|
||||
<Box key={q.id} sx={{ mb: 4 }}>
|
||||
<Typography variant="h6">{q.title}</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
{q.description}
|
||||
</Typography>
|
||||
|
||||
<LearningAnswers
|
||||
type={q.type as "single" | "multiple" | "text"}
|
||||
variants={q.variants}
|
||||
correctAnswers={q.correct_answers}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
{(user?.type == "admin" || user?.type == "creator") && (
|
||||
<span>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
||||
<Typography variant="h4">{test.title} results</Typography>
|
||||
</Box>
|
||||
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>Test</TableCell>
|
||||
<TableCell>Test Taker</TableCell>
|
||||
<TableCell>Test Author</TableCell>
|
||||
<TableCell>Score</TableCell>
|
||||
<TableCell>Completed</TableCell>
|
||||
<TableCell>Created At</TableCell>
|
||||
<TableCell>Closed At</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{tests?.data && tests.data.length > 0 ? (
|
||||
tests.data.map((ut) => (
|
||||
<UserTestRow
|
||||
key={ut.id}
|
||||
userTest={ut}
|
||||
onDelete={(id) => setDeleteId(id)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={9} align="center" sx={{ py: 3 }}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
No results found for this test.
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Pagination
|
||||
color="primary"
|
||||
shape="rounded"
|
||||
count={tests?.meta.last_page}
|
||||
page={currentPage}
|
||||
onChange={(_, value) => setCurrentPage(value)}
|
||||
sx={{
|
||||
mt: 3,
|
||||
mb: 3,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
<Dialog open={deleteId !== null} onClose={() => setDeleteId(null)}>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
Are you sure you want to delete this user test?
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteId(null)}>Cancel</Button>
|
||||
<Button
|
||||
color="error"
|
||||
onClick={handleDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</span>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@ -8,9 +8,12 @@ import { useSubmitAnswer } from "../../hooks/Tests/useSubmitAnswer";
|
||||
import { useCompleteTest } from "../../hooks/Tests/useCompleteTest";
|
||||
import LearningAnswers from "../../components/Answers/Answers";
|
||||
import { formatDate } from "../../utils/functions";
|
||||
import NotFoundPage from "../NotFoundPage/NotFoundPage";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
|
||||
export const TestPage = () => {
|
||||
const { id } = useParams();
|
||||
const { user } = useAuth();
|
||||
const { data: test, isLoading, error } = useUserTestById(Number(id));
|
||||
const [currentQuestion, setCurrentQuestion] = useState(1);
|
||||
const submitAnswerMutation = useSubmitAnswer();
|
||||
@ -30,25 +33,11 @@ export const TestPage = () => {
|
||||
</Container>
|
||||
);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h6" color="error">
|
||||
Oops! An error occurred: {error.message}
|
||||
</Typography>
|
||||
</Container>
|
||||
);
|
||||
if (error) return <NotFoundPage />;
|
||||
|
||||
if (!test)
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h6" color="textSecondary">
|
||||
No test found.
|
||||
</Typography>
|
||||
</Container>
|
||||
);
|
||||
if (!test) return <NotFoundPage />;
|
||||
|
||||
if (!test.is_available && !test.is_completed)
|
||||
if (!test.is_available && !test.is_completed && user?.type == 'user')
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="h6" color="textSecondary">
|
||||
|
||||
@ -12,7 +12,6 @@ import { useUserById } from "../../hooks/users/useUserById";
|
||||
import { useCreateUser } from "../../hooks/users/useCreateUser";
|
||||
import { useUpdateUser } from "../../hooks/users/useUpdateUser";
|
||||
|
||||
|
||||
const UserForm = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isUpdate = Boolean(id);
|
||||
@ -46,8 +45,6 @@ const UserForm = () => {
|
||||
const payload = { username, email, type, ...(password && { password }) };
|
||||
|
||||
if (isUpdate && id) {
|
||||
console.log(payload);
|
||||
console.log(id);
|
||||
updateUserMutation.mutate(
|
||||
{ id: Number(id), payload },
|
||||
{ onSuccess: () => navigate("/dashboard/users") }
|
||||
@ -103,6 +100,7 @@ const UserForm = () => {
|
||||
<MenuItem value="user">User</MenuItem>
|
||||
<MenuItem value="admin">Admin</MenuItem>
|
||||
<MenuItem value="creator">Creator</MenuItem>
|
||||
<MenuItem value="banned">Banned</MenuItem>
|
||||
</TextField>
|
||||
<Button variant="contained" color="primary" type="submit">
|
||||
{isUpdate ? "Update" : "Create"}
|
||||
|
||||
@ -20,6 +20,8 @@ import CategoriesPage from "../pages/CategoriesPage/CategoriesPage";
|
||||
import CategoryForm from "../pages/CategoryForm/CategoryForm";
|
||||
import LogsPage from "../pages/LogsPage/LogsPage";
|
||||
import HitCountsPage from "../pages/HitcountsPage/HitcountsPage";
|
||||
import UserTestsPage from "../pages/AdminUserTestsPage/AdminUserTestsPage";
|
||||
import { SingleTestPage } from "../pages/SingleTestPage/SingleTestPage";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -70,6 +72,10 @@ const router = createBrowserRouter([
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{path: "/tests/view/:id",
|
||||
element: <SingleTestPage/>
|
||||
},
|
||||
|
||||
{
|
||||
path: "/tests",
|
||||
element: <TestsPage />,
|
||||
@ -95,7 +101,7 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{ path: "questions", element: <Container>questions</Container> },
|
||||
{ path: "tests", element: <Container>tests</Container> },
|
||||
{ path: "user-tests", element: <Container>User Tests</Container> },
|
||||
{ path: "user-tests", element:<UserTestsPage/> },
|
||||
{
|
||||
path: "categories",
|
||||
children: [
|
||||
|
||||
@ -12,7 +12,7 @@ export function arraysEqual(a: number[], b: number[]): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export const formatDate = (dateString: string | undefined) => {
|
||||
export const formatDate = (dateString: string | undefined | null) => {
|
||||
if (!dateString) return "N/A";
|
||||
|
||||
const date = new Date(dateString);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user