Added assigmnet api endpoint and ui table.
This commit is contained in:
parent
a2a12a0f00
commit
c9dc3eda2c
@ -2,6 +2,7 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { account } from "../lib/appwrite";
|
import { account } from "../lib/appwrite";
|
||||||
import { useTheme } from "../context/ThemeContext";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
|
import { useSidebar } from "../context/SidebarContext"; // Add this import
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
const { darkMode, toggleDarkMode } = useTheme();
|
const { darkMode, toggleDarkMode } = useTheme();
|
||||||
@ -12,6 +13,7 @@ const { darkMode, toggleDarkMode } = useTheme();
|
|||||||
const [notifying, setNotifying] = useState(true); // assuming initial notifying state is true
|
const [notifying, setNotifying] = useState(true); // assuming initial notifying state is true
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { isOpen, toggleSidebar } = useSidebar(); // Use the sidebar context
|
||||||
|
|
||||||
// ------fetch user-----------------
|
// ------fetch user-----------------
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -80,7 +82,7 @@ useEffect(() => {
|
|||||||
{/* Left side - Menu Button */}
|
{/* Left side - Menu Button */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMenuOpen(!isMenuOpen)}
|
onClick={toggleSidebar}
|
||||||
className={`z-99999 flex h-10 w-10 items-center justify-center rounded-lg border-gray-200 text-gray-500 dark:border-gray-800 dark:text-gray-400 lg:h-11 lg:w-11 lg:border ${isMenuOpen
|
className={`z-99999 flex h-10 w-10 items-center justify-center rounded-lg border-gray-200 text-gray-500 dark:border-gray-800 dark:text-gray-400 lg:h-11 lg:w-11 lg:border ${isMenuOpen
|
||||||
? 'lg:bg-transparent dark:lg:bg-transparent bg-gray-100 dark:bg-gray-800'
|
? 'lg:bg-transparent dark:lg:bg-transparent bg-gray-100 dark:bg-gray-800'
|
||||||
: ''
|
: ''
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "../context/ThemeContext";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
|
import { useSidebar } from "../context/SidebarContext";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const [showFormsSubmenu, setShowFormsSubmenu] = useState(false);
|
|
||||||
const { darkMode } = useTheme();
|
const { darkMode } = useTheme();
|
||||||
|
const { isCollapsed } = useSidebar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={`w-[290px] h-full border-r flex flex-col ${darkMode ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200'
|
<aside className={`h-full border-r flex flex-col transition-all duration-200 ease-in-out
|
||||||
}`}>
|
${isCollapsed ? 'w-20' : 'w-[290px]'}
|
||||||
{/* Logo */}
|
${darkMode ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200'}`}>
|
||||||
<div className="py-8 flex justify-start">
|
|
||||||
|
{/* Logo - Only shown when expanded */}
|
||||||
|
{!isCollapsed && (
|
||||||
|
<div className="py-8 flex justify-start px-4">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<Image
|
<Image
|
||||||
src={darkMode ? "/images/logo/logo-dark.svg" : "/images/logo/logo.svg"}
|
src={darkMode ? "/images/logo/logo-dark.svg" : "/images/logo/logo.svg"}
|
||||||
@ -22,20 +26,24 @@ const Sidebar = () => {
|
|||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<div className="flex flex-col overflow-y-auto no-scrollbar">
|
<div className="flex flex-col overflow-y-auto no-scrollbar">
|
||||||
<nav className="mb-6">
|
<nav className="mb-6 px-2">
|
||||||
<div className="flex flex-col gap-4">
|
{/* Menu heading - Only shown when expanded */}
|
||||||
<h2 className="mb-4 text-xs uppercase leading-[20px] text-gray-400 dark:text-gray-500">
|
{!isCollapsed && (
|
||||||
|
<h2 className="mb-4 text-xs uppercase leading-[20px] text-gray-400 dark:text-gray-500 px-2">
|
||||||
Menu
|
Menu
|
||||||
</h2>
|
</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
{/* Dashboard */}
|
{/* Dashboard */}
|
||||||
<div>
|
|
||||||
<Link
|
<Link
|
||||||
href="/pages/dashboard"
|
href="/pages/dashboard"
|
||||||
className={`flex items-center w-full p-2 rounded-lg ${darkMode
|
className={`flex items-center w-full p-2 rounded-lg group
|
||||||
|
${darkMode
|
||||||
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
||||||
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
@ -43,16 +51,23 @@ const Sidebar = () => {
|
|||||||
<img
|
<img
|
||||||
src="/images/icons/grid.svg"
|
src="/images/icons/grid.svg"
|
||||||
alt="Dashboard"
|
alt="Dashboard"
|
||||||
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
|
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-left">Dashboard</span>
|
{!isCollapsed && <span className="flex-1 text-left">Dashboard</span>}
|
||||||
|
{isCollapsed && (
|
||||||
|
<span className={`absolute left-full ml-2 px-2 py-1 text-xs rounded-md shadow-lg
|
||||||
|
${darkMode ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap`}>
|
||||||
|
Dashboard
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* -------------table01 page--------------- */}
|
{/* table01 page */}
|
||||||
<Link
|
<Link
|
||||||
href="/pages/table01"
|
href="/pages/table01"
|
||||||
className={`flex items-center w-full p-2 rounded-lg ${darkMode
|
className={`flex items-center w-full p-2 rounded-lg group
|
||||||
|
${darkMode
|
||||||
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
||||||
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
@ -60,14 +75,23 @@ const Sidebar = () => {
|
|||||||
<img
|
<img
|
||||||
src="/file.svg"
|
src="/file.svg"
|
||||||
alt="table01"
|
alt="table01"
|
||||||
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
|
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-left">table01</span>
|
{!isCollapsed && <span className="flex-1 text-left">table01</span>}
|
||||||
|
{isCollapsed && (
|
||||||
|
<span className={`absolute left-full ml-2 px-2 py-1 text-xs rounded-md shadow-lg
|
||||||
|
${darkMode ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap`}>
|
||||||
|
table01
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{/* ---------------TemporaryTable-------------- */}
|
|
||||||
|
{/* register employee */}
|
||||||
<Link
|
<Link
|
||||||
href="/pages/register-employee"
|
href="/pages/register-employee"
|
||||||
className={`flex items-center w-full p-2 rounded-lg ${darkMode
|
className={`flex items-center w-full p-2 rounded-lg group
|
||||||
|
${darkMode
|
||||||
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
||||||
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
@ -75,14 +99,23 @@ const Sidebar = () => {
|
|||||||
<img
|
<img
|
||||||
src="/file.svg"
|
src="/file.svg"
|
||||||
alt="register employee"
|
alt="register employee"
|
||||||
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
|
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-left">register employee</span>
|
{!isCollapsed && <span className="flex-1 text-left">register employee</span>}
|
||||||
|
{isCollapsed && (
|
||||||
|
<span className={`absolute left-full ml-2 px-2 py-1 text-xs rounded-md shadow-lg
|
||||||
|
${darkMode ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap`}>
|
||||||
|
register employee
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{/* --------------register employee--------- */}
|
|
||||||
|
{/* TemporaryTable */}
|
||||||
<Link
|
<Link
|
||||||
href="/pages/TemporaryTable"
|
href="/pages/TemporaryTable"
|
||||||
className={`flex items-center w-full p-2 rounded-lg ${darkMode
|
className={`flex items-center w-full p-2 rounded-lg group
|
||||||
|
${darkMode
|
||||||
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
||||||
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
@ -90,15 +123,23 @@ const Sidebar = () => {
|
|||||||
<img
|
<img
|
||||||
src="/file.svg"
|
src="/file.svg"
|
||||||
alt="TemporaryTable"
|
alt="TemporaryTable"
|
||||||
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
|
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-left">TemporaryTable</span>
|
{!isCollapsed && <span className="flex-1 text-left">TemporaryTable</span>}
|
||||||
|
{isCollapsed && (
|
||||||
|
<span className={`absolute left-full ml-2 px-2 py-1 text-xs rounded-md shadow-lg
|
||||||
|
${darkMode ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap`}>
|
||||||
|
TemporaryTable
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{/* ----------------------- */}
|
|
||||||
{/* ----------Trainees Data------------- */}
|
{/* Trainees Data */}
|
||||||
<Link
|
<Link
|
||||||
href="/pages/TraineesData"
|
href="/pages/TraineesData"
|
||||||
className={`flex items-center w-full p-2 rounded-lg ${darkMode
|
className={`flex items-center w-full p-2 rounded-lg group
|
||||||
|
${darkMode
|
||||||
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
? 'text-gray-300 hover:text-white hover:bg-gray-800'
|
||||||
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
|
||||||
}`}
|
}`}
|
||||||
@ -106,11 +147,17 @@ const Sidebar = () => {
|
|||||||
<img
|
<img
|
||||||
src="/file.svg"
|
src="/file.svg"
|
||||||
alt="TraineesData"
|
alt="TraineesData"
|
||||||
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
|
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 text-left">Trainees Data</span>
|
{!isCollapsed && <span className="flex-1 text-left">Trainees Data</span>}
|
||||||
|
{isCollapsed && (
|
||||||
|
<span className={`absolute left-full ml-2 px-2 py-1 text-xs rounded-md shadow-lg
|
||||||
|
${darkMode ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap`}>
|
||||||
|
Trainees Data
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
{/* ----------------------- */}
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,63 +1,32 @@
|
|||||||
|
// src/app/context/SidebarContext.js
|
||||||
'use client';
|
'use client';
|
||||||
import { useState } from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import { account } from "../lib/appwrite";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
export default function AuthForm() {
|
const SidebarContext = createContext();
|
||||||
const [isLogin, setIsLogin] = useState(true);
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
export function SidebarProvider({ children }) {
|
||||||
e.preventDefault();
|
const [isCollapsed, setIsCollapsed] = useState(false); // Changed from isOpen to isCollapsed
|
||||||
|
|
||||||
try {
|
const toggleSidebar = () => setIsCollapsed(!isCollapsed);
|
||||||
if (isLogin) {
|
const collapseSidebar = () => setIsCollapsed(true);
|
||||||
await account.createEmailPasswordSession(email, password);
|
const expandSidebar = () => setIsCollapsed(false);
|
||||||
} else {
|
|
||||||
await account.create('unique()', email, password);
|
|
||||||
await account.createEmailPasswordSession(email, password);
|
|
||||||
}
|
|
||||||
router.push("/pages/dashboard");
|
|
||||||
} catch (err) {
|
|
||||||
alert("Authentication error: " + err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto mt-10 p-6 border rounded-lg shadow-lg">
|
<SidebarContext.Provider value={{
|
||||||
<h2 className="text-2xl font-bold mb-4">{isLogin ? 'Login' : 'Register'}</h2>
|
isCollapsed,
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
toggleSidebar,
|
||||||
<input
|
collapseSidebar,
|
||||||
type="email"
|
expandSidebar
|
||||||
placeholder="Email"
|
}}>
|
||||||
className="w-full border px-3 py-2 rounded"
|
{children}
|
||||||
value={email}
|
</SidebarContext.Provider>
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Password"
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button type="submit" className="w-full bg-blue-600 text-white py-2 rounded">
|
|
||||||
{isLogin ? "Login" : "Register"}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<p className="mt-4 text-sm text-center">
|
|
||||||
{isLogin ? "New user?" : "Already registered?"}{" "}
|
|
||||||
<button
|
|
||||||
onClick={() => setIsLogin(!isLogin)}
|
|
||||||
className="text-blue-500 underline"
|
|
||||||
>
|
|
||||||
{isLogin ? "Register here" : "Login here"}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useSidebar() {
|
||||||
|
const context = useContext(SidebarContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useSidebar must be used within a SidebarProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
@ -6,6 +6,7 @@ import Navbar from "./components/Navbar";
|
|||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import { AuthProvider } from "./context/AuthContext";
|
import { AuthProvider } from "./context/AuthContext";
|
||||||
import { ThemeProvider,useTheme } from "./context/ThemeContext";
|
import { ThemeProvider,useTheme } from "./context/ThemeContext";
|
||||||
|
import { SidebarProvider } from './context/SidebarContext';
|
||||||
|
|
||||||
function LayoutContent({ children }) {
|
function LayoutContent({ children }) {
|
||||||
const { darkMode } = useTheme();
|
const { darkMode } = useTheme();
|
||||||
@ -48,7 +49,9 @@ export default function RootLayout({ children }) {
|
|||||||
<link rel="icon" href="/images/brand/brand-08.svg" />
|
<link rel="icon" href="/images/brand/brand-08.svg" />
|
||||||
</head>
|
</head>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<SidebarProvider>
|
||||||
<LayoutContent>{children}</LayoutContent>
|
<LayoutContent>{children}</LayoutContent>
|
||||||
|
</SidebarProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -2,21 +2,24 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHeader,
|
||||||
|
TableRow
|
||||||
|
} from '../../ui/Table';
|
||||||
|
|
||||||
export default function TraineesDataPage() {
|
export default function TraineesDataPage() {
|
||||||
const [trainees, setTrainees] = useState([]);
|
const [trainees, setTrainees] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('https://dtsnew.adhcloud.space/items/trainees', {
|
fetch(`${process.env.NEXT_PUBLIC_API_URL}/items/trainees`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer G_2h5o1NlX1_PfomkUe5zk7Xx0dT2juV',
|
Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// fetch('http://localhost:8055/items/trainees')
|
|
||||||
|
|
||||||
|
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTrainees(data.data || []);
|
setTrainees(data.data || []);
|
||||||
@ -26,7 +29,7 @@ export default function TraineesDataPage() {
|
|||||||
console.error('Error fetching trainees:', err);
|
console.error('Error fetching trainees:', err);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="p-6">
|
<main className="p-6">
|
||||||
@ -34,17 +37,34 @@ export default function TraineesDataPage() {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="space-y-2">
|
<div className="overflow-auto rounded-lg border border-gray-200 bg-white">
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="border-b border-gray-100">
|
||||||
|
<TableRow>
|
||||||
|
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500">Name</TableCell>
|
||||||
|
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500">Batch</TableCell>
|
||||||
|
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500">DOJ</TableCell>
|
||||||
|
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500">Speed</TableCell>
|
||||||
|
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500">Accuracy</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody className="divide-y divide-gray-100">
|
||||||
{trainees.map((trainee) => (
|
{trainees.map((trainee) => (
|
||||||
<li key={trainee.id} className="border p-4 rounded-md shadow">
|
<TableRow key={trainee.id}>
|
||||||
|
<TableCell className="px-5 py-4 text-blue-600 hover:underline">
|
||||||
<Link href={`/pages/TraineesData/trainee/${trainee.id}`}>
|
<Link href={`/pages/TraineesData/trainee/${trainee.id}`}>
|
||||||
<span className="text-blue-600 hover:underline cursor-pointer">
|
|
||||||
{trainee.Name}
|
{trainee.Name}
|
||||||
</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</TableCell>
|
||||||
|
<TableCell className="px-5 py-4">{trainee.batch_name}</TableCell>
|
||||||
|
<TableCell className="px-5 py-4">{trainee.doj}</TableCell>
|
||||||
|
<TableCell className="px-5 py-4">{trainee.speed}</TableCell>
|
||||||
|
<TableCell className="px-5 py-4">{trainee.accuracy}</TableCell>
|
||||||
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
@ -1,29 +1,12 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
// async function getTrainee(id) {
|
|
||||||
// try {
|
|
||||||
// const res = await fetch(`http://localhost:8055/items/trainees/${id}`);
|
|
||||||
// const json = await res.json();
|
|
||||||
// return json.data;
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Error fetching trainee:', error);
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
async function getTrainee(id) {
|
async function getTrainee(id) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://dtsnew.adhcloud.space/items/trainees/${id}`, {
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/items/trainees/${id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer G_2h5o1NlX1_PfomkUe5zk7Xx0dT2juV',
|
Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// if we are using directus API
|
|
||||||
// const res = await fetch(`http://localhost:8055/items/trainees/${id}`);
|
|
||||||
|
|
||||||
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
return json.data;
|
return json.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -32,19 +15,36 @@ async function getTrainee(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAssignmentsByTrainee(traineeId) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_API_URL}/items/assignment?filter[sub_by][_eq]=${traineeId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const json = await res.json();
|
||||||
|
return json.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching assignments:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ Destructure 'params' at the top level
|
|
||||||
export default async function TraineeDetailPage({ params: { id } }) {
|
export default async function TraineeDetailPage({ params: { id } }) {
|
||||||
const trainee = await getTrainee(id);
|
const trainee = await getTrainee(id);
|
||||||
|
const assignments = await getAssignmentsByTrainee(id);
|
||||||
|
|
||||||
if (!trainee) return notFound();
|
if (!trainee) return notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="p-6">
|
<main className="p-6">
|
||||||
<h1 className="text-2xl font-bold mb-4">{trainee.Name}</h1>
|
<h1 className="text-2xl font-bold mb-4">{trainee.Name}</h1>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-xl font-semibold mb-2">Trainee Details</h2>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
<li><strong>Feedback:</strong> {trainee.overall_feedback}</li>
|
<li><strong>Feedback:</strong> {trainee.overall_feedback}</li>
|
||||||
<li><strong>Attendance:</strong> {trainee.overall_attendance}</li>
|
<li><strong>Attendance:</strong> {trainee.overall_attendance}</li>
|
||||||
@ -54,6 +54,86 @@ export default async function TraineeDetailPage({ params: { id } }) {
|
|||||||
<li><strong>Precision:</strong> {trainee.precision}</li>
|
<li><strong>Precision:</strong> {trainee.precision}</li>
|
||||||
<li><strong>DOJ:</strong> {trainee.doj}</li>
|
<li><strong>DOJ:</strong> {trainee.doj}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold mb-2">Assignments</h2>
|
||||||
|
{assignments.length === 0 ? (
|
||||||
|
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
||||||
|
<div className="flex">
|
||||||
|
<div className="ml-3">
|
||||||
|
<p className="text-sm text-yellow-700">
|
||||||
|
No assignments found for this trainee. This could mean:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-5 mt-1 text-sm text-yellow-700">
|
||||||
|
<li>The trainee hasn't submitted any assignments yet</li>
|
||||||
|
<li>Assignments exist but are associated with a different ID</li>
|
||||||
|
<li>There might be a data connection issue</li>
|
||||||
|
</ul>
|
||||||
|
<div className="mt-2 text-xs text-yellow-600">
|
||||||
|
<p>Debug info:</p>
|
||||||
|
<p>Trainee ID: {id}</p>
|
||||||
|
<p>Total assignments in system: {assignments.length}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{assignments.map((assignment, index) => (
|
||||||
|
<div key={index} className="border p-4 rounded-md shadow">
|
||||||
|
<h3 className="font-medium mb-2">Assignment {index + 1}</h3>
|
||||||
|
<p><strong>Format:</strong> {assignment.format || 'Not specified'}</p>
|
||||||
|
{assignment.link && (
|
||||||
|
<p>
|
||||||
|
<strong>Main Link:</strong>{" "}
|
||||||
|
<a
|
||||||
|
href={assignment.link.includes('://') ? assignment.link : `https://${assignment.link}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
{assignment.link}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<h4 className="font-medium">Submissions:</h4>
|
||||||
|
{assignment.assignments && assignment.assignments.length > 0 ? (
|
||||||
|
<ul className="space-y-2 mt-2">
|
||||||
|
{assignment.assignments.map((submission, subIndex) => (
|
||||||
|
<li key={subIndex} className="border-l-2 pl-3 py-1">
|
||||||
|
<p>
|
||||||
|
<strong>Submission {subIndex + 1}:</strong>{" "}
|
||||||
|
{submission.link_or_zip?.includes('://') ? (
|
||||||
|
<a
|
||||||
|
href={submission.link_or_zip}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
{submission.link_or_zip}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
submission.link_or_zip || 'No link provided'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{submission.info && (
|
||||||
|
<p className="mt-1"><strong>Feedback:</strong> {submission.info}</p>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-500">No submissions yet</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user