submission from employee side done.

This commit is contained in:
ATUL GUNJAL 2025-04-09 17:14:27 +05:30
parent a7b6b78890
commit 26663b1c58
8 changed files with 526 additions and 164 deletions

View File

@ -4,57 +4,103 @@ import { useEffect, useState } from "react";
import { databases } from "../lib/appwrite"; import { databases } from "../lib/appwrite";
import { databaseId, membersCollectionId } from "../lib/appwrite"; import { databaseId, membersCollectionId } from "../lib/appwrite";
import { getCurrentUserWithRole } from "../utils/auth"; import { getCurrentUserWithRole } from "../utils/auth";
import { Query } from "appwrite";
export default function MemberList() { export default function MemberList() {
const [members, setMembers] = useState([]); const [members, setMembers] = useState([]);
const [user, setUser] = useState(null); const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
getCurrentUserWithRole().then(setUser); const fetchData = async () => {
}, []);
useEffect(() => {
const getMembers = async () => {
try { try {
// Get current user with role first
const user = await getCurrentUserWithRole();
setCurrentUser(user);
// Then fetch members with proper error handling
const res = await databases.listDocuments( const res = await databases.listDocuments(
databaseId, databaseId,
membersCollectionId membersCollectionId,
[Query.orderDesc("$createdAt")] // Optional: sort by creation date
); );
setMembers(res.documents); // <-- Add this line
setMembers(res.documents);
} catch (err) { } catch (err) {
console.error("Error fetching members:", err); console.error("Error fetching data:", err);
setError("Failed to load member data");
} finally {
setLoading(false);
} }
}; };
getMembers(); fetchData();
}, []); }, []);
return ( if (loading) {
<div className="mt-8"> return <div className="p-4">Loading members...</div>;
<h2 className="text-xl font-semibold mb-4">All Members</h2> }
<table className="w-full border">
<thead>
<tr className="bg-gray-100">
<th className="p-2 text-left">Name</th>
<th className="p-2">Phone</th>
<th className="p-2">Join Date</th>
<th className="p-2">Role</th>
</tr>
</thead>
<tbody>
{members.map((m) => (
<tr key={m.$id} className="border-t">
<td className="p-2">{m.name}</td>
<td className="p-2">{m.phone}</td>
<td className="p-2">{m.joinDate}</td>
<td className="p-2">{m.role}</td>
</tr>
))}
</tbody>
</table>
{/* Render role only if user is loaded */} if (error) {
{user ? <p className="mt-4">Role: {user.role}</p> : <p>Loading role...</p>} return <div className="p-4 text-red-500">{error}</div>;
}
return (
<div className="mt-8 p-4">
<h2 className="text-xl font-semibold mb-4">All Members</h2>
{/* Current user info */}
{currentUser && (
<div className="mb-6 p-4 bg-blue-50 rounded-lg">
<p className="font-medium">You are logged in as: {currentUser.name}</p>
<p>Your role: <span className="capitalize font-semibold">{currentUser.role}</span></p>
</div>
)}
{/* Members table */}
<div className="overflow-x-auto">
<table className="w-full border">
<thead>
<tr className="bg-gray-100">
<th className="p-3 text-left">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Phone</th>
<th className="p-3">Join Date</th>
<th className="p-3">Role</th>
</tr>
</thead>
<tbody>
{members.length > 0 ? (
members.map((member) => (
<tr key={member.$id} className="border-t hover:bg-gray-50">
<td className="p-3">{member.name || "N/A"}</td>
<td className="p-3">{member.email || "N/A"}</td>
<td className="p-3">{member.phone || "N/A"}</td>
<td className="p-3">
{member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"}
</td>
<td className="p-3 capitalize">
<span className={`px-2 py-1 rounded-full text-xs ${
member.role === "admin"
? "bg-purple-100 text-purple-800"
: "bg-blue-100 text-blue-800"
}`}>
{member.role || "unknown"}
</span>
</td>
</tr>
))
) : (
<tr>
<td colSpan="5" className="p-4 text-center text-gray-500">
No members found
</td>
</tr>
)}
</tbody>
</table>
</div>
</div> </div>
); );
} }

View File

@ -9,7 +9,7 @@ client
const account = new Account(client); const account = new Account(client);
const databases = new Databases(client); const databases = new Databases(client);
// Import from .env // Import from .env
const databaseId = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID; const databaseId = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID;
const membersCollectionId = process.env.NEXT_PUBLIC_COLLECTION_MEMBERS_ID; const membersCollectionId = process.env.NEXT_PUBLIC_COLLECTION_MEMBERS_ID;

View File

@ -1,18 +0,0 @@
// app/admin/members/layout.js
export const metadata = {
title: "Manage Members | Admin",
description: "Add or view team members in the portal",
};
export default function AdminLayout({ children }) {
return (
<section className="p-6">
<h1 className="text-2xl font-bold mb-4">Team Members</h1>
<div className="bg-white shadow-md rounded-lg p-4">
{children}
</div>
</section>
);
}

View File

@ -1,18 +0,0 @@
// app/admin/members/layout.js
export const metadata = {
title: "Manage Members | Admin",
description: "Add or view team members in the portal",
};
export default function MembersLayout({ children }) {
return (
<section className="p-6">
<h1 className="text-2xl font-bold mb-4">Team Members</h1>
<div className="bg-white shadow-md rounded-lg p-4">
{children}
</div>
</section>
);
}

View File

@ -1,120 +1,225 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { databases, account } from "../../lib/appwrite"; import { databases } from "../../lib/appwrite";
import { ID } from "appwrite"; import { ID } from "appwrite";
import { getCurrentUserWithRole } from "../../utils/auth";
export default function AssignmentsPage() { export default function AssignmentsPage() {
const [assignments, setAssignments] = useState([]); const [assignments, setAssignments] = useState([]);
const [submissionLinks, setSubmissionLinks] = useState({}); const [submissionLinks, setSubmissionLinks] = useState({});
const [userId, setUserId] = useState(null); const [currentUser, setCurrentUser] = useState(null);
const [loadingUser, setLoadingUser] = useState(true); // 🟡 Loading state const [loading, setLoading] = useState(true);
const [newAssignment, setNewAssignment] = useState({
title: "",
deadline: ""
});
// Fetch assignments and user on mount // Fetch assignments and user
useEffect(() => { useEffect(() => {
const fetchAssignments = async () => { const fetchData = async () => {
try { try {
const response = await databases.listDocuments( const [user, assignmentsResponse] = await Promise.all([
"67e1452b00016444b37f", getCurrentUserWithRole(),
"67f0f285001a1614b4e8" databases.listDocuments(
); "67e1452b00016444b37f",
setAssignments(response.documents); "67f0f285001a1614b4e8"
)
]);
setCurrentUser(user);
setAssignments(assignmentsResponse.documents);
} catch (error) { } catch (error) {
console.error("Error fetching assignments:", error); console.error("Error:", error);
}
};
const fetchUser = async () => {
try {
const user = await account.get();
setUserId(user.$id);
} catch (error) {
console.error("User not logged in:", error);
} finally { } finally {
setLoadingUser(false); // ✅ Done loading setLoading(false);
} }
}; };
fetchAssignments(); fetchData();
fetchUser();
}, []); }, []);
const handleInputChange = (assignmentId, value) => { const handleCreateAssignment = async () => {
setSubmissionLinks({ if (!currentUser || currentUser.role !== 'admin') {
...submissionLinks, alert("Only admins can create assignments");
[assignmentId]: value,
});
};
const handleSubmit = async (assignmentId) => {
if (!userId) {
alert("User not logged in. Cannot submit.");
return;
}
const submissionLink = submissionLinks[assignmentId];
if (!submissionLink) {
alert("Submission link is empty.");
return; return;
} }
try { try {
await databases.createDocument( await databases.createDocument(
"67e1452b00016444b37f", "67e1452b00016444b37f",
"67f5ea0a000393d31806", // ✅ Replace with correct ID "67f0f285001a1614b4e8",
ID.unique(), ID.unique(),
{ {
assignmentId, title: newAssignment.title,
userId, deadline: newAssignment.deadline,
submissionLink, userId: currentUser.$id, // Add current user's ID
submitted: false,
submittedLate: false,
submittedLink: ""
} }
); );
alert("Submitted!");
// Refresh assignments
const updated = await databases.listDocuments(
"67e1452b00016444b37f",
"67f0f285001a1614b4e8"
);
setAssignments(updated.documents);
setNewAssignment({ title: "", deadline: "" });
alert("Assignment created successfully!");
} catch (error) { } catch (error) {
console.error("Submission failed:", error); console.error("Creation failed:", error);
alert("Submission failed: " + error.message); alert(`Error: ${error.message}`);
} }
}; };
if (loadingUser) { const handleSubmitAssignment = async (assignmentId) => {
return <div>Loading...</div>; // Optional loading indicator const link = submissionLinks[assignmentId];
} if (!link) {
alert("Please provide a submission link");
return;
}
try {
const isLate = new Date() > new Date(
assignments.find(a => a.$id === assignmentId).deadline
);
await databases.updateDocument(
"67e1452b00016444b37f",
"67f0f285001a1614b4e8",
assignmentId,
{
submitted: true,
submittedLate: isLate,
submittedLink: link
}
);
// Refresh assignments
const updated = await databases.listDocuments(
"67e1452b00016444b37f",
"67f0f285001a1614b4e8"
);
setAssignments(updated.documents);
setSubmissionLinks({...submissionLinks, [assignmentId]: ""});
alert(`Submission ${isLate ? 'completed (late)' : 'completed on time'}`);
} catch (error) {
console.error("Submission failed:", error);
alert("Submission failed");
}
};
if (loading) return <div>Loading...</div>;
return ( return (
<div> <div className="p-4">
<h1 className="text-2xl font-bold mb-4">Assignments</h1> <h1 className="text-2xl font-bold mb-4">Assignments</h1>
{currentUser && (
<p className="mb-4">Role: <span className="capitalize font-semibold">{currentUser.role}</span></p>
)}
{assignments.length === 0 ? ( {/* Admin-only creation form */}
<p>No assignments available.</p> {currentUser?.role === 'admin' && (
) : ( <div className="mb-6 p-4 border rounded-lg bg-gray-50">
assignments.map((assignment) => ( <h2 className="text-xl font-semibold mb-4">Create New Assignment</h2>
<div key={assignment.$id} className="mb-6 border p-4 rounded-lg"> <div className="space-y-4">
<h2 className="text-xl font-semibold">{assignment.title}</h2> <div>
<p className="text-gray-600">{assignment.description}</p> <label className="block text-sm font-medium mb-1">Title*</label>
<p className="text-sm text-gray-400"> <input
Deadline: {assignment.deadline} type="text"
</p> value={newAssignment.title}
onChange={(e) => setNewAssignment({...newAssignment, title: e.target.value})}
<input className="w-full border px-3 py-2 rounded"
type="text" required
className="mt-2 border px-2 py-1 w-full" />
placeholder="Paste your GitHub/Firebase/Netlify link" </div>
value={submissionLinks[assignment.$id] || ""} <div>
onChange={(e) => <label className="block text-sm font-medium mb-1">Deadline*</label>
handleInputChange(assignment.$id, e.target.value) <input
} type="datetime-local"
/> value={newAssignment.deadline}
onChange={(e) => setNewAssignment({...newAssignment, deadline: e.target.value})}
className="border px-3 py-2 rounded"
required
/>
</div>
<button <button
onClick={() => handleSubmit(assignment.$id)} onClick={handleCreateAssignment}
className="mt-2 bg-blue-600 text-white px-4 py-1 rounded" className="bg-green-600 text-white px-4 py-2 rounded disabled:bg-gray-400"
disabled={!submissionLinks[assignment.$id]} disabled={!newAssignment.title || !newAssignment.deadline}
> >
Submit Create Assignment
</button> </button>
</div> </div>
)) </div>
)} )}
{/* Assignments list */}
<div className="space-y-4">
{assignments.length === 0 ? (
<p>No assignments available.</p>
) : (
assignments.map((assignment) => (
<div key={assignment.$id} className="border p-4 rounded-lg">
<div className="flex justify-between items-start">
<div>
<h2 className="text-xl font-semibold">{assignment.title}</h2>
<p className="text-sm text-gray-600 mt-1">
Deadline: {new Date(assignment.deadline).toLocaleString()}
</p>
</div>
{assignment.submitted && (
<span className={`px-2 py-1 text-xs rounded-full ${
assignment.submittedLate
? 'bg-red-100 text-red-800'
: 'bg-green-100 text-green-800'
}`}>
{assignment.submittedLate ? 'Late' : 'Submitted'}
</span>
)}
</div>
{!assignment.submitted && currentUser?.role !== 'admin' && (
<div className="mt-4">
<input
type="text"
placeholder="Paste submission link"
value={submissionLinks[assignment.$id] || ""}
onChange={(e) => setSubmissionLinks({
...submissionLinks,
[assignment.$id]: e.target.value
})}
className="w-full border px-3 py-2 rounded"
/>
<button
onClick={() => handleSubmitAssignment(assignment.$id)}
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded"
disabled={!submissionLinks[assignment.$id]}
>
Submit
</button>
</div>
)}
{assignment.submitted && assignment.submittedLink && (
<div className="mt-3">
<a
href={assignment.submittedLink}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
View Submission
</a>
</div>
)}
</div>
))
)}
</div>
</div> </div>
); );
} }

View File

@ -0,0 +1,226 @@
"use client";
import { useEffect, useState } from "react";
import { databases } from "../../lib/appwrite";
import { ID, Query } from "appwrite";
import { getCurrentUserWithRole } from "../../utils/auth";
export default function SubmissionPage() {
const [assignments, setAssignments] = useState([]);
const [submissionLink, setSubmissionLink] = useState("");
const [selectedAssignment, setSelectedAssignment] = useState("");
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
// Database IDs
const databaseId = "67e1452b00016444b37f";
const assignmentsCollectionId = "67f0f285001a1614b4e8";
const submissionsCollectionId = "67f5ea0a000393d31806";
const employeeCollectionId = "67f143f00010e2cd2652"; // FSPL HR Employee Management
useEffect(() => {
const fetchData = async () => {
try {
const [user, assignmentsResponse] = await Promise.all([
getCurrentUserWithRole(),
databases.listDocuments(databaseId, assignmentsCollectionId, [
Query.equal("submitted", false)
])
]);
setCurrentUser(user);
setAssignments(assignmentsResponse.documents);
} catch (error) {
console.error("Error:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const handleSubmit = async () => {
if (!selectedAssignment || !submissionLink || !currentUser?.$id) {
alert("Please fill all required fields");
return;
}
try {
// 1. Create the submission document
await databases.createDocument(
databaseId,
submissionsCollectionId,
ID.unique(),
{
assignmentId: selectedAssignment,
userId: currentUser.$id,
submissionLink
}
);
// 2. Update the assignment status
const assignment = assignments.find(a => a.$id === selectedAssignment);
await databases.updateDocument(
databaseId,
assignmentsCollectionId,
selectedAssignment,
{
submitted: true,
submittedLate: new Date() > new Date(assignment.deadline),
submittedLink: submissionLink
}
);
// 3. Optionally update employee record if needed
try {
await databases.updateDocument(
databaseId,
employeeCollectionId,
currentUser.$id, // Assuming document ID matches user ID
{
lastSubmission: new Date().toISOString(),
lastSubmissionLink: submissionLink
}
);
} catch (employeeError) {
console.log("Employee record not updated (optional):", employeeError);
}
alert("Submission successful!");
setSubmissionLink("");
setSelectedAssignment("");
// Refresh assignments list
const updated = await databases.listDocuments(
databaseId,
assignmentsCollectionId,
[Query.equal("submitted", false)]
);
setAssignments(updated.documents);
} catch (error) {
console.error("Submission failed:", error);
alert(`Submission failed: ${error.message}`);
}
};
if (loading) return <div className="p-4">Loading...</div>;
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-6">Submit Assignment</h1>
<div className="space-y-4 max-w-md">
<div>
<label className="block text-sm font-medium mb-1">Select Assignment*</label>
<select
value={selectedAssignment}
onChange={(e) => setSelectedAssignment(e.target.value)}
className="w-full border px-3 py-2 rounded"
required
>
<option value="">-- Select Assignment --</option>
{assignments.map((assignment) => (
<option key={assignment.$id} value={assignment.$id}>
{assignment.title} (Due: {new Date(assignment.deadline).toLocaleDateString()})
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1">Submission Link*</label>
<input
type="url"
placeholder="https://github.com/your-repo"
value={submissionLink}
onChange={(e) => setSubmissionLink(e.target.value)}
className="w-full border px-3 py-2 rounded"
required
/>
<p className="text-xs text-gray-500 mt-1">GitHub, Gitea, or other repository link</p>
</div>
<button
onClick={handleSubmit}
className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 disabled:bg-gray-400"
disabled={!selectedAssignment || !submissionLink}
>
Submit Assignment
</button>
</div>
{/* Previous Submissions Section */}
<div className="mt-8">
<h2 className="text-xl font-semibold mb-4">Your Submissions</h2>
<div className="border rounded-lg overflow-hidden">
<table className="w-full">
<thead className="bg-gray-100">
<tr>
<th className="p-3 text-left">Assignment</th>
<th className="p-3 text-left">Submission Link</th>
<th className="p-3 text-left">Submitted At</th>
</tr>
</thead>
<tbody>
{currentUser?.$id && (
<SubmissionList
userId={currentUser.$id}
assignments={assignments}
/>
)}
</tbody>
</table>
</div>
</div>
</div>
);
}
// Separate component for fetching and displaying submissions
function SubmissionList({ userId, assignments }) {
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSubmissions = async () => {
try {
const response = await databases.listDocuments(
"67e1452b00016444b37f",
"67f5ea0a000393d31806",
[Query.equal("userId", userId)]
);
setSubmissions(response.documents);
} catch (error) {
console.error("Error fetching submissions:", error);
} finally {
setLoading(false);
}
};
fetchSubmissions();
}, [userId]);
if (loading) return <tr><td colSpan="3" className="p-4 text-center">Loading submissions...</td></tr>;
if (submissions.length === 0) return <tr><td colSpan="3" className="p-4 text-center">No submissions found</td></tr>;
return submissions.map((submission) => {
const assignment = assignments.find(a => a.$id === submission.assignmentId);
return (
<tr key={submission.$id} className="border-t hover:bg-gray-50">
<td className="p-3">{assignment?.title || submission.assignmentId}</td>
<td className="p-3">
<a
href={submission.submissionLink}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
View Submission
</a>
</td>
<td className="p-3">
{new Date(submission.$createdAt).toLocaleString()}
</td>
</tr>
);
});
}

View File

@ -9,31 +9,52 @@ const databases = new Databases(client);
export async function getCurrentUserWithRole() { export async function getCurrentUserWithRole() {
try { try {
// 1. Get current user from Auth
const user = await account.get(); const user = await account.get();
if (!user || !user.$id) { if (!user || !user.$id) {
throw new Error("User not authenticated"); throw new Error("User not authenticated");
} }
// 2. Database constants
const dbId = "67e1452b00016444b37f"; const dbId = "67e1452b00016444b37f";
const collectionId = "67f0f1200006897dc192"; const collectionId = "67f0f1200006897dc192"; // Your employee collection
const response = await databases.listDocuments(dbId, collectionId, [ // 3. Try to get employee record
Query.equal("userId", user.$id), let role = "unknown"; // Default role if not found
]); let userDoc = null;
try {
const response = await databases.listDocuments(dbId, collectionId, [
Query.equal("userId", user.$id),
Query.limit(1)
]);
const userDoc = response.documents[0]; if (response.documents.length > 0) {
userDoc = response.documents[0];
role = userDoc.role || "employee"; // Use "employee" if role exists but is empty
}
} catch (dbError) {
console.error("Database error:", dbError);
// Continue with default role
}
// 4. Return combined data
return { return {
$id: user.$id, // Make sure to include this ...user, // All original user properties
name: user.name, role, // Determined role ("admin", "employee", or "unknown")
email: user.email, isAdmin: role === "admin", // Convenience boolean
role: userDoc?.role || "unknown", employeeDoc: userDoc // Full employee document if available
// Include the document ID if needed
docId: userDoc?.$id
}; };
} catch (error) { } catch (error) {
console.error("Error fetching user with role:", error); console.error("Error in getCurrentUserWithRole:", error);
throw error; // Throw instead of returning null
// Convert Appwrite errors to more user-friendly messages
if (error.type === "general_unauthorized_scope") {
throw new Error("Please login to access this feature");
}
throw error; // Re-throw for components to handle
} }
} }