"use client"; import React, { useState, useRef } from "react"; // The roles in UserSeed are ALL CAPS as per your data type Role = "ADMIN" | "GUEST" | "SCIENTIST"; // Forward declarations if you don't have these yet // You can replace Scientist or User with real types if you have them type Scientist = { id: number; createdAt: Date | string; name: string; level: string; // "JUNIOR" | "SENIOR" userId: number; user: User; superiorId?: number | null; superior?: Scientist | null; subordinates: Scientist[]; earthquakes: Earthquake[]; observatories: Observatory[]; artefacts: Artefact[]; }; type Earthquake = { id: number; createdAt: Date | string; updatedAt: Date | string; date: Date | string; location: string; latitude: string; longitude: string; magnitude: number; depth: number; creatorId?: number | null; creator?: Scientist | null; artefacts: Artefact[]; observatories: Observatory[]; }; type Observatory = { id: number; createdAt: Date | string; updatedAt: Date | string; name: string; location: string; longitude: string; latitude: string; dateEstablished?: number | null; functional: boolean; seismicSensorOnline: boolean; creatorId?: number | null; creator?: Scientist | null; earthquakes: Earthquake[]; }; type Artefact = { id: number; createdAt: Date | string; updatedAt: Date | string; type: string; warehouseArea: string; earthquakeId: number; earthquake: Earthquake; creatorId?: number | null; creator?: Scientist | null; required: boolean; shopPrice?: number | null; purchasedById?: number | null; purchasedBy?: User | null; pickedUp: boolean; }; type User= { id: number; createdAt: string; name: string; email: string; passwordHash: string; role: Role; // "ADMIN" | "GUEST" | "SCIENTIST" scientist : Scientist | null purchasedArtefacts: Artefact[]; }; // Example users with scientist references const initialUsers: User[] = [ { id: 1, createdAt: "2024-06-20T08:00:00Z", name: "Dr. Alice Volcano", email: "alice.volcano@example.com", passwordHash: "hashed_password_1", role: "SCIENTIST", scientist: { id: 101, createdAt: "2024-06-18T09:00:00Z", name: "Dr. Alice Volcano", level: "SENIOR", userId: 1, user: null as any, // Will be populated after object creation to avoid circular reference, see note below superiorId: null, superior: null, subordinates: [], earthquakes: [ { id: 201, createdAt: "2024-06-01T04:00:00Z", updatedAt: "2024-06-10T10:00:00Z", date: "2024-06-01T05:00:00Z", location: "Fuego Ridge", latitude: "14.23", longitude: "-90.88", magnitude: 7.2, depth: 10.1, creatorId: 101, creator: null, artefacts: [], observatories: [], } ], observatories: [ { id: 301, createdAt: "2024-01-01T08:00:00Z", updatedAt: "2024-06-05T16:30:00Z", name: "Central Vulcanology Lab", location: "Fuego City", longitude: "-90.88", latitude: "14.23", dateEstablished: 2011, functional: true, seismicSensorOnline: true, creatorId: 101, creator: null, earthquakes: [], } ], artefacts: [ { id: 401, createdAt: "2024-06-11T09:00:00Z", updatedAt: "2024-06-12T10:45:00Z", type: "Lava", warehouseArea: "ZoneA-Shelf1", earthquakeId: 201, earthquake: { id: 201, createdAt: "2024-06-01T04:00:00Z", updatedAt: "2024-06-10T10:00:00Z", date: "2024-06-01T05:00:00Z", location: "Fuego Ridge", latitude: "14.23", longitude: "-90.88", magnitude: 7.2, depth: 10.1, creatorId: 101, creator: null, artefacts: [], observatories: [], }, creatorId: 101, creator: null, required: true, shopPrice: 500.0, purchasedById: null, purchasedBy: null, pickedUp: false, } ], }, purchasedArtefacts: [], }, { id: 2, createdAt: "2024-06-21T08:00:00Z", name: "Dr. Bob Lava", email: "bob.lava@example.com", passwordHash: "hashed_password_2", role: "SCIENTIST", scientist: { id: 102, createdAt: "2024-06-19T13:00:00Z", name: "Dr. Bob Lava", level: "JUNIOR", userId: 2, user: null as any, // Populated after object creation if circular needed superiorId: 101, superior: null, subordinates: [], earthquakes: [], observatories: [], artefacts: [], }, purchasedArtefacts: [], } ]; // Optionally, populate the scientist.user field with the parent user (avoiding circular reference on first creation): if (initialUsers[0].scientist && initialUsers[1].scientist){ initialUsers[0].scientist.user = initialUsers[0]; initialUsers[1].scientist.user = initialUsers[1]; initialUsers[1].scientist.superior = initialUsers[1].scientist;} const allArtefacts: Artefact[] = [ { id: 401, createdAt: "2024-06-11T09:00:00Z", updatedAt: "2024-06-12T10:45:00Z", type: "Lava", warehouseArea: "ZoneA-Shelf1", earthquakeId: 201, earthquake: { id: 201, createdAt: "2024-06-01T04:00:00Z", updatedAt: "2024-06-10T10:00:00Z", date: "2024-06-01T05:00:00Z", location: "Fuego Ridge", latitude: "14.23", longitude: "-90.88", magnitude: 7.2, depth: 10.1, creatorId: 101, creator: null, artefacts: [], observatories: [], }, creatorId: 101, creator: null, required: true, shopPrice: 500.0, purchasedById: null, purchasedBy: null, pickedUp: false, } ]; const allEarthquakes: Earthquake[] = [ { id: 201, createdAt: "2024-06-01T04:00:00Z", updatedAt: "2024-06-10T10:00:00Z", date: "2024-06-01T05:00:00Z", location: "Fuego Ridge", latitude: "14.23", longitude: "-90.88", magnitude: 7.2, depth: 10.1, creatorId: 101, creator: null, artefacts: [], observatories: [], } ]; const allScientists: Scientist[] = initialUsers.map(u => u.scientist as Scientist).filter(Boolean); // Human readable role labels const roleLabels: Record = { ADMIN: "Admin", GUEST: "Guest", SCIENTIST: "Scientist" }; const allRoles: Role[] = ["ADMIN", "GUEST", "SCIENTIST"]; const sortFields = [ { label: "Name", value: "name" }, { label: "Email", value: "email" } ] as const; type SortField = (typeof sortFields)[number]["value"]; type SortDir = "asc" | "desc"; const dirLabels: Record = { asc: "ascending", desc: "descending" }; const fieldLabels: Record = { name: "Name", email: "Email" }; export default function AdminPage() { const [users, setUsers] = useState(initialUsers); const [selectedEmail, setSelectedEmail] = useState(null); const [editUser, setEditUser] = useState(null); const [showPassword, setShowPassword] = useState(false); const [showAssignmentPanel, setShowAssignmentPanel] = useState(null); React.useEffect(() => { if (!selectedEmail) setEditUser(null); else { const user = users.find(u => u.email === selectedEmail); // Add a dummy password field just for edit box (not stored) setEditUser(user ? { ...user, password: "" } : null); } }, [selectedEmail, users]); // Search/filter/sort state const [searchField, setSearchField] = useState("name"); const [searchText, setSearchText] = useState(""); // The filter UI must use ALL CAPS for roles in UserSeed const [roleFilter, setRoleFilter] = useState("all"); const [sortField, setSortField] = useState("name"); const [sortDir, setSortDir] = useState("asc"); const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [sortDropdownOpen, setSortDropdownOpen] = useState(false); const filterDropdownRef = useRef(null); const sortDropdownRef = useRef(null); // Dropdown auto-close React.useEffect(() => { const handleClick = (e: MouseEvent) => { if ( filterDropdownRef.current && !filterDropdownRef.current.contains(e.target as Node) ) setFilterDropdownOpen(false); if ( sortDropdownRef.current && !sortDropdownRef.current.contains(e.target as Node) ) setSortDropdownOpen(false); }; document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, []); // Handler: assign/unassign scientist const handleAssignScientist = (scientistId: number) => { setEditUser(prev => prev ? { ...prev, scientist: allScientists.find(s => s.id === scientistId) || null, } : null ); }; // Filter, search, sort logic const filteredUsers = users.filter( (user) => roleFilter === "all" || user.role === roleFilter ); const searchedUsers = filteredUsers.filter(user => user[searchField].toLowerCase().includes(searchText.toLowerCase()) ); const sortedUsers = [...searchedUsers].sort((a, b) => { let cmp = a[sortField].localeCompare(b[sortField]); return sortDir === "asc" ? cmp : -cmp; }); // Edit form handler (special-case password, don't modify passwordHash) const handleEditChange = (e: React.ChangeEvent) => { if (!editUser) return; const { name, value } = e.target; setEditUser(prev => prev ? { ...prev, [name]: value } : null ); }; // Selected "actual" user object const selectedUser = users.find(u => u.email === selectedEmail); const isEditChanged = React.useMemo(() => { if (!editUser || !selectedUser) return false; return ( editUser.name !== selectedUser.name || editUser.role !== selectedUser.role || (editUser.password && editUser.password !== "") // Only trigger update if changed ); }, [editUser, selectedUser]); // Update/save changes const handleUpdate = (e: React.FormEvent) => { e.preventDefault(); if (!editUser) return; setUsers(prev => prev.map(u => u.email === editUser.email ? { ...editUser } // Copies ALL user properties, including scientist, artefacts, etc. : u ) ); }; // Delete user const handleDelete = () => { if (!selectedUser) return; if (!window.confirm(`Are you sure you want to delete "${selectedUser.name}"? This cannot be undone.`)) return; setUsers(prev => prev.filter(u => u.email !== selectedUser.email)); setSelectedEmail(null); setEditUser(null); }; const [showEmailTooltip, setShowEmailTooltip] = useState(false); return (
{/* SIDEBAR */}
{/* Search Bar */}
setSearchText(e.target.value)} />
{/* Filter and Sort Buttons */}
{/* Filter */}
{filterDropdownOpen && (
{allRoles.map(role => ( ))}
)}
{/* Sort */}
{sortDropdownOpen && (
{sortFields.map(opt => ( ))}
)}
{/* Asc/Desc Toggle */}
{/* Sort status text */} Users sorted by {fieldLabels[sortField]} {dirLabels[sortDir]} {/* USERS LIST: full height, scrollable */}
    {sortedUsers.map((user) => (
  • setSelectedEmail(user.email)} className={`rounded-lg cursor-pointer border ${selectedEmail === user.email ? "bg-blue-100 border-blue-400" : "hover:bg-gray-200 border-transparent"} transition px-2 py-1 mb-1`} >
    {user.name} {roleLabels[user.role]}
    {user.email}
  • ))} {sortedUsers.length === 0 && (
  • No users found.
  • )}
{/* MAIN PANEL */}
{editUser ? (

Edit User

{ e.preventDefault(); }}> {/* Creation Time */}
{editUser.createdAt}
{/* Email */}
{/* Name */}
setEditUser(prev => prev ? { ...prev, name: e.target.value } : null)} />
{/* Role */}
{/* Scientist assignment */}
{/* Show all scientist data */} {editUser.scientist && (
{editUser.scientist ? `${editUser.scientist.name} (${editUser.scientist.level})` : None }
)}
{/* Password - eye toggle only (view, not edit) */}
{/* Trigger artefact assignment panel */}
Currently assigned: {editUser.purchasedArtefacts.map(a => a.type).join(", ") || "None"}
{/* Trigger earthquake assignment panel */}
{editUser.scientist ? "Current: " + (editUser.scientist.earthquakes.map(e => e.location).join(", ") || "None") : "Assign a scientist to manage earthquakes"}
) : (
Select a user...
)} {/* Right-side assignment panel */} {showAssignmentPanel && editUser &&(
{showAssignmentPanel === 'artefact' ? 'Assign Artefacts' : 'Assign Earthquakes'}
{showAssignmentPanel === 'artefact' && ( <> {allArtefacts.map(a => ( ))} )} {showAssignmentPanel === 'earthquake' && editUser &&( <> {!editUser.scientist ?
Assign a scientist first.
: allEarthquakes.map(eq => ( ))} )} {showAssignmentPanel === 'scientist' && editUser.scientist &&( <> {/* Scientist edit form */}
{/* Name */}
setEditUser(prev => prev && prev.scientist ? { ...prev, scientist: { ...prev.scientist, name: e.target.value } } : prev) } />
{/* Level */}
{/* Assign/Remove Superior */}
{/* Subordinates readonly */}
{editUser.scientist.subordinates.length ? editUser.scientist.subordinates.map(sc => sc.name).join(", ") : None}
)}
)}
); }