Added assigmnet api endpoint and ui table.

This commit is contained in:
ATUL GUNJAL 2025-06-06 16:11:49 +05:30
parent a2a12a0f00
commit c9dc3eda2c
6 changed files with 293 additions and 172 deletions

View File

@ -2,6 +2,7 @@
import { useState, useEffect, useRef } from "react";
import { account } from "../lib/appwrite";
import { useTheme } from "../context/ThemeContext";
import { useSidebar } from "../context/SidebarContext"; // Add this import
const Navbar = () => {
const { darkMode, toggleDarkMode } = useTheme();
@ -12,6 +13,7 @@ const { darkMode, toggleDarkMode } = useTheme();
const [notifying, setNotifying] = useState(true); // assuming initial notifying state is true
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const { isOpen, toggleSidebar } = useSidebar(); // Use the sidebar context
// ------fetch user-----------------
useEffect(() => {
@ -80,7 +82,7 @@ useEffect(() => {
{/* Left side - Menu Button */}
<div className="flex items-center gap-4">
<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
? 'lg:bg-transparent dark:lg:bg-transparent bg-gray-100 dark:bg-gray-800'
: ''

View File

@ -1,58 +1,73 @@
"use client";
import React, { useState } from "react";
import React from "react";
import Link from "next/link";
import Image from "next/image";
import { useTheme } from "../context/ThemeContext";
import { useSidebar } from "../context/SidebarContext";
const Sidebar = () => {
const [showFormsSubmenu, setShowFormsSubmenu] = useState(false);
const { darkMode } = useTheme();
const { isCollapsed } = useSidebar();
return (
<aside className={`w-[290px] h-full border-r flex flex-col ${darkMode ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200'
}`}>
{/* Logo */}
<div className="py-8 flex justify-start">
<Link href="/">
<Image
src={darkMode ? "/images/logo/logo-dark.svg" : "/images/logo/logo.svg"}
alt="Logo"
width={150}
height={40}
/>
</Link>
</div>
<aside className={`h-full border-r flex flex-col transition-all duration-200 ease-in-out
${isCollapsed ? 'w-20' : 'w-[290px]'}
${darkMode ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200'}`}>
{/* Logo - Only shown when expanded */}
{!isCollapsed && (
<div className="py-8 flex justify-start px-4">
<Link href="/">
<Image
src={darkMode ? "/images/logo/logo-dark.svg" : "/images/logo/logo.svg"}
alt="Logo"
width={150}
height={40}
/>
</Link>
</div>
)}
{/* Navigation */}
<div className="flex flex-col overflow-y-auto no-scrollbar">
<nav className="mb-6">
<div className="flex flex-col gap-4">
<h2 className="mb-4 text-xs uppercase leading-[20px] text-gray-400 dark:text-gray-500">
<nav className="mb-6 px-2">
{/* Menu heading - Only shown when expanded */}
{!isCollapsed && (
<h2 className="mb-4 text-xs uppercase leading-[20px] text-gray-400 dark:text-gray-500 px-2">
Menu
</h2>
)}
<div className="flex flex-col gap-1">
{/* Dashboard */}
<div>
<Link
href="/pages/dashboard"
className={`flex items-center w-full p-2 rounded-lg ${darkMode
? 'text-gray-300 hover:text-white hover:bg-gray-800'
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
}`}
>
<img
src="/images/icons/grid.svg"
alt="Dashboard"
className={`w-5 h-5 mr-3 ${darkMode ? 'filter invert' : ''}`}
/>
<span className="flex-1 text-left">Dashboard</span>
</Link>
</div>
<Link
href="/pages/dashboard"
className={`flex items-center w-full p-2 rounded-lg group
${darkMode
? 'text-gray-300 hover:text-white hover:bg-gray-800'
: 'text-gray-600 hover:text-brand-500 hover:bg-gray-100'
}`}
>
<img
src="/images/icons/grid.svg"
alt="Dashboard"
className={`w-5 h-5 ${isCollapsed ? 'mx-auto' : 'mr-3'} ${darkMode ? 'filter invert' : ''}`}
/>
{!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>
{/* -------------table01 page--------------- */}
{/* table01 page */}
<Link
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-600 hover:text-brand-500 hover:bg-gray-100'
}`}
@ -60,14 +75,23 @@ const Sidebar = () => {
<img
src="/file.svg"
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>
{/* ---------------TemporaryTable-------------- */}
{/* register employee */}
<Link
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-600 hover:text-brand-500 hover:bg-gray-100'
}`}
@ -75,14 +99,23 @@ const Sidebar = () => {
<img
src="/file.svg"
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>
{/* --------------register employee--------- */}
{/* TemporaryTable */}
<Link
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-600 hover:text-brand-500 hover:bg-gray-100'
}`}
@ -90,15 +123,23 @@ const Sidebar = () => {
<img
src="/file.svg"
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>
{/* ----------------------- */}
{/* ----------Trainees Data------------- */}
{/* Trainees Data */}
<Link
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-600 hover:text-brand-500 hover:bg-gray-100'
}`}
@ -106,11 +147,17 @@ const Sidebar = () => {
<img
src="/file.svg"
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>
{/* ----------------------- */}
</div>
</nav>
</div>

View File

@ -1,63 +1,32 @@
// src/app/context/SidebarContext.js
'use client';
import { useState } from "react";
import { account } from "../lib/appwrite";
import { useRouter } from "next/navigation";
import { createContext, useContext, useState } from "react";
export default function AuthForm() {
const [isLogin, setIsLogin] = useState(true);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const SidebarContext = createContext();
const handleSubmit = async (e) => {
e.preventDefault();
export function SidebarProvider({ children }) {
const [isCollapsed, setIsCollapsed] = useState(false); // Changed from isOpen to isCollapsed
try {
if (isLogin) {
await account.createEmailPasswordSession(email, password);
} else {
await account.create('unique()', email, password);
await account.createEmailPasswordSession(email, password);
}
router.push("/pages/dashboard");
} catch (err) {
alert("Authentication error: " + err.message);
}
};
const toggleSidebar = () => setIsCollapsed(!isCollapsed);
const collapseSidebar = () => setIsCollapsed(true);
const expandSidebar = () => setIsCollapsed(false);
return (
<div className="max-w-md mx-auto mt-10 p-6 border rounded-lg shadow-lg">
<h2 className="text-2xl font-bold mb-4">{isLogin ? 'Login' : 'Register'}</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input
type="email"
placeholder="Email"
className="w-full border px-3 py-2 rounded"
value={email}
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>
<SidebarContext.Provider value={{
isCollapsed,
toggleSidebar,
collapseSidebar,
expandSidebar
}}>
{children}
</SidebarContext.Provider>
);
}
export function useSidebar() {
const context = useContext(SidebarContext);
if (!context) {
throw new Error('useSidebar must be used within a SidebarProvider');
}
return context;
}

View File

@ -6,6 +6,7 @@ import Navbar from "./components/Navbar";
import Sidebar from "./components/Sidebar";
import { AuthProvider } from "./context/AuthContext";
import { ThemeProvider,useTheme } from "./context/ThemeContext";
import { SidebarProvider } from './context/SidebarContext';
function LayoutContent({ children }) {
const { darkMode } = useTheme();
@ -48,7 +49,9 @@ export default function RootLayout({ children }) {
<link rel="icon" href="/images/brand/brand-08.svg" />
</head>
<ThemeProvider>
<SidebarProvider>
<LayoutContent>{children}</LayoutContent>
</SidebarProvider>
</ThemeProvider>
</html>
);

View File

@ -2,31 +2,34 @@
import { useEffect, useState } from 'react';
import Link from 'next/link';
import {
Table,
TableBody,
TableCell,
TableHeader,
TableRow
} from '../../ui/Table';
export default function TraineesDataPage() {
const [trainees, setTrainees] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://dtsnew.adhcloud.space/items/trainees', {
headers: {
Authorization: 'Bearer G_2h5o1NlX1_PfomkUe5zk7Xx0dT2juV',
},
})
// fetch('http://localhost:8055/items/trainees')
.then((res) => res.json())
.then((data) => {
setTrainees(data.data || []);
setLoading(false);
useEffect(() => {
fetch(`${process.env.NEXT_PUBLIC_API_URL}/items/trainees`, {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`,
},
})
.catch((err) => {
console.error('Error fetching trainees:', err);
setLoading(false);
});
}, []);
.then((res) => res.json())
.then((data) => {
setTrainees(data.data || []);
setLoading(false);
})
.catch((err) => {
console.error('Error fetching trainees:', err);
setLoading(false);
});
}, []);
return (
<main className="p-6">
@ -34,20 +37,37 @@ export default function TraineesDataPage() {
{loading ? (
<p>Loading...</p>
) : (
<ul className="space-y-2">
{trainees.map((trainee) => (
<li key={trainee.id} className="border p-4 rounded-md shadow">
<Link href={`/pages/TraineesData/trainee/${trainee.id}`}>
<span className="text-blue-600 hover:underline cursor-pointer">
{trainee.Name}
</span>
</Link>
</li>
))}
</ul>
<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) => (
<TableRow key={trainee.id}>
<TableCell className="px-5 py-4 text-blue-600 hover:underline">
<Link href={`/pages/TraineesData/trainee/${trainee.id}`}>
{trainee.Name}
</Link>
</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>
))}
</TableBody>
</Table>
</div>
)}
</main>
);
}
export const dynamic = 'force-dynamic';
export const dynamic = 'force-dynamic';

View File

@ -1,29 +1,12 @@
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) {
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: {
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();
return json.data;
} catch (error) {
@ -32,28 +15,125 @@ 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 } }) {
const trainee = await getTrainee(id);
const assignments = await getAssignmentsByTrainee(id);
if (!trainee) return notFound();
return (
<main className="p-6">
<h1 className="text-2xl font-bold mb-4">{trainee.Name}</h1>
<ul className="space-y-1">
<li><strong>Feedback:</strong> {trainee.overall_feedback}</li>
<li><strong>Attendance:</strong> {trainee.overall_attendance}</li>
<li><strong>Batch:</strong> {trainee.batch_name}</li>
<li><strong>Speed:</strong> {trainee.speed}</li>
<li><strong>Accuracy:</strong> {trainee.accuracy}</li>
<li><strong>Precision:</strong> {trainee.precision}</li>
<li><strong>DOJ:</strong> {trainee.doj}</li>
</ul>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">Trainee Details</h2>
<ul className="space-y-1">
<li><strong>Feedback:</strong> {trainee.overall_feedback}</li>
<li><strong>Attendance:</strong> {trainee.overall_attendance}</li>
<li><strong>Batch:</strong> {trainee.batch_name}</li>
<li><strong>Speed:</strong> {trainee.speed}</li>
<li><strong>Accuracy:</strong> {trainee.accuracy}</li>
<li><strong>Precision:</strong> {trainee.precision}</li>
<li><strong>DOJ:</strong> {trainee.doj}</li>
</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>
);
}
}