Added many todos
This commit is contained in:
parent
1b0b751b32
commit
efc16aa92a
@ -50,7 +50,6 @@ model Earthquake {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
date DateTime
|
date DateTime
|
||||||
code String @unique
|
code String @unique
|
||||||
magnitude Float
|
magnitude Float
|
||||||
@ -59,10 +58,8 @@ model Earthquake {
|
|||||||
longitude Float
|
longitude Float
|
||||||
location String
|
location String
|
||||||
depth String
|
depth String
|
||||||
|
|
||||||
creatorId Int?
|
creatorId Int?
|
||||||
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id])
|
creator Scientist? @relation("ScientistEarthquakeCreator", fields: [creatorId], references: [id])
|
||||||
|
|
||||||
artefacts Artefact[]
|
artefacts Artefact[]
|
||||||
observatories Observatory[] @relation("EarthquakeObservatory")
|
observatories Observatory[] @relation("EarthquakeObservatory")
|
||||||
}
|
}
|
||||||
@ -91,6 +88,7 @@ model Artefact {
|
|||||||
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
type String @db.VarChar(50) // Lava, Tephra, Ash, Soil
|
||||||
warehouseArea String
|
warehouseArea String
|
||||||
description String
|
description String
|
||||||
|
imagePath String
|
||||||
earthquakeId Int
|
earthquakeId Int
|
||||||
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
earthquake Earthquake @relation(fields: [earthquakeId], references: [id])
|
||||||
creatorId Int?
|
creatorId Int?
|
||||||
@ -101,6 +99,8 @@ model Artefact {
|
|||||||
isSold Boolean @default(false)
|
isSold Boolean @default(false)
|
||||||
purchasedById Int?
|
purchasedById Int?
|
||||||
purchasedBy User? @relation("UserPurchasedArtefacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
purchasedBy User? @relation("UserPurchasedArtefacts", fields: [purchasedById], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
// todo unlink purchase from user
|
||||||
|
// todo link purchase to order
|
||||||
isCollected Boolean @default(false)
|
isCollected Boolean @default(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,3 +111,11 @@ model Pallet {
|
|||||||
warehouseArea String
|
warehouseArea String
|
||||||
palletNote String
|
palletNote String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Order {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
orderNumber String
|
||||||
|
// todo link order to user
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useRef } from "react";
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
type Role = "ADMIN" | "GUEST" | "SCIENTIST";
|
type Role = "ADMIN" | "GUEST" | "SCIENTIST";
|
||||||
const roleLabels: Record<Role, string> = {
|
const roleLabels: Record<Role, string> = {
|
||||||
ADMIN: "Admin",
|
ADMIN: "Admin",
|
||||||
@ -15,11 +16,27 @@ type User = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// todo create api route to get users, with auth for only admin
|
||||||
|
// todo add management of only junior scientists if senior scientist
|
||||||
const initialUsers: User[] = [
|
const initialUsers: User[] = [
|
||||||
{ email: "john@example.com", name: "John Doe", role: "ADMIN", password: "secret1", createdAt: "2024-06-21T09:15:01Z", id: 1 },
|
{ email: "john@example.com", name: "John Doe", role: "ADMIN", password: "secret1", createdAt: "2024-06-21T09:15:01Z", id: 1 },
|
||||||
{ email: "jane@example.com", name: "Jane Smith", role: "GUEST", password: "secret2", createdAt: "2024-06-21T10:01:09Z", id: 2 },
|
{ email: "jane@example.com", name: "Jane Smith", role: "GUEST", password: "secret2", createdAt: "2024-06-21T10:01:09Z", id: 2 },
|
||||||
{ email: "bob@example.com", name: "Bob Brown", role: "SCIENTIST", password: "secret3", createdAt: "2024-06-21T12:13:45Z" ,id:3},
|
{
|
||||||
{ email: "alice@example.com", name: "Alice Johnson",role: "GUEST", password: "secret4", createdAt: "2024-06-20T18:43:20Z" ,id:4},
|
email: "bob@example.com",
|
||||||
|
name: "Bob Brown",
|
||||||
|
role: "SCIENTIST",
|
||||||
|
password: "secret3",
|
||||||
|
createdAt: "2024-06-21T12:13:45Z",
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: "alice@example.com",
|
||||||
|
name: "Alice Johnson",
|
||||||
|
role: "GUEST",
|
||||||
|
password: "secret4",
|
||||||
|
createdAt: "2024-06-20T18:43:20Z",
|
||||||
|
id: 4,
|
||||||
|
},
|
||||||
{ email: "eve@example.com", name: "Eve Black", role: "ADMIN", password: "secret5", createdAt: "2024-06-20T19:37:10Z", id: 5 },
|
{ email: "eve@example.com", name: "Eve Black", role: "ADMIN", password: "secret5", createdAt: "2024-06-20T19:37:10Z", id: 5 },
|
||||||
{ email: "dave@example.com", name: "Dave Clark", role: "GUEST", password: "pw", createdAt: "2024-06-19T08:39:10Z", id: 6 },
|
{ email: "dave@example.com", name: "Dave Clark", role: "GUEST", password: "pw", createdAt: "2024-06-19T08:39:10Z", id: 6 },
|
||||||
{ email: "fred@example.com", name: "Fred Fox", role: "GUEST", password: "pw", createdAt: "2024-06-19T09:11:52Z", id: 7 },
|
{ email: "fred@example.com", name: "Fred Fox", role: "GUEST", password: "pw", createdAt: "2024-06-19T09:11:52Z", id: 7 },
|
||||||
@ -30,12 +47,13 @@ const initialUsers: User[] = [
|
|||||||
{ email: "leo@example.com", name: "Leo Garrison", role: "GUEST", password: "pw", createdAt: "2024-06-12T08:02:51Z", id: 12 },
|
{ email: "leo@example.com", name: "Leo Garrison", role: "GUEST", password: "pw", createdAt: "2024-06-12T08:02:51Z", id: 12 },
|
||||||
{ email: "isaac@example.com", name: "Isaac Yang", role: "GUEST", password: "pw", createdAt: "2024-06-12T15:43:29Z", id: 13 },
|
{ email: "isaac@example.com", name: "Isaac Yang", role: "GUEST", password: "pw", createdAt: "2024-06-12T15:43:29Z", id: 13 },
|
||||||
];
|
];
|
||||||
const sortFields = [ // Sort box options
|
const sortFields = [
|
||||||
|
// Sort box options
|
||||||
{ label: "Name", value: "name" },
|
{ label: "Name", value: "name" },
|
||||||
{ label: "Email", value: "email" },
|
{ label: "Email", value: "email" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type SortField = typeof sortFields[number]["value"];
|
type SortField = (typeof sortFields)[number]["value"];
|
||||||
type SortDir = "asc" | "desc";
|
type SortDir = "asc" | "desc";
|
||||||
const dirLabels: Record<SortDir, string> = { asc: "ascending", desc: "descending" };
|
const dirLabels: Record<SortDir, string> = { asc: "ascending", desc: "descending" };
|
||||||
const fieldLabels: Record<SortField, string> = { name: "Name", email: "Email" };
|
const fieldLabels: Record<SortField, string> = { name: "Name", email: "Email" };
|
||||||
@ -50,7 +68,7 @@ export default function AdminPage() {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!selectedEmail) setEditUser(null);
|
if (!selectedEmail) setEditUser(null);
|
||||||
else {
|
else {
|
||||||
const user = users.find(u => u.email === selectedEmail);
|
const user = users.find((u) => u.email === selectedEmail);
|
||||||
setEditUser(user ? { ...user } : null);
|
setEditUser(user ? { ...user } : null);
|
||||||
}
|
}
|
||||||
}, [selectedEmail, users]);
|
}, [selectedEmail, users]);
|
||||||
@ -69,24 +87,16 @@ export default function AdminPage() {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleClick = (e: MouseEvent) => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
if (
|
if (filterDropdownRef.current && !filterDropdownRef.current.contains(e.target as Node)) setFilterDropdownOpen(false);
|
||||||
filterDropdownRef.current && !filterDropdownRef.current.contains(e.target as Node)
|
if (sortDropdownRef.current && !sortDropdownRef.current.contains(e.target as Node)) setSortDropdownOpen(false);
|
||||||
) setFilterDropdownOpen(false);
|
|
||||||
if (
|
|
||||||
sortDropdownRef.current && !sortDropdownRef.current.contains(e.target as Node)
|
|
||||||
) setSortDropdownOpen(false);
|
|
||||||
};
|
};
|
||||||
document.addEventListener("mousedown", handleClick);
|
document.addEventListener("mousedown", handleClick);
|
||||||
return () => document.removeEventListener("mousedown", handleClick);
|
return () => document.removeEventListener("mousedown", handleClick);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Filtering, searching, sorting logic
|
// Filtering, searching, sorting logic
|
||||||
const filteredUsers = users.filter(
|
const filteredUsers = users.filter((user) => roleFilter === "all" || user.role === roleFilter);
|
||||||
(user) => roleFilter === "all" || user.role === roleFilter
|
const searchedUsers = filteredUsers.filter((user) => user[searchField].toLowerCase().includes(searchText.toLowerCase()));
|
||||||
);
|
|
||||||
const searchedUsers = filteredUsers.filter(user =>
|
|
||||||
user[searchField].toLowerCase().includes(searchText.toLowerCase())
|
|
||||||
);
|
|
||||||
const sortedUsers = [...searchedUsers].sort((a, b) => {
|
const sortedUsers = [...searchedUsers].sort((a, b) => {
|
||||||
let cmp = a[sortField].localeCompare(b[sortField]);
|
let cmp = a[sortField].localeCompare(b[sortField]);
|
||||||
return sortDir === "asc" ? cmp : -cmp;
|
return sortDir === "asc" ? cmp : -cmp;
|
||||||
@ -96,9 +106,7 @@ export default function AdminPage() {
|
|||||||
const handleEditChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
const handleEditChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
if (!editUser) return;
|
if (!editUser) return;
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setEditUser(prev =>
|
setEditUser((prev) => (prev ? { ...prev, [name]: value } : null));
|
||||||
prev ? { ...prev, [name]: value } : null
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update button logic (compare original selectedUser and editUser)
|
// Update button logic (compare original selectedUser and editUser)
|
||||||
@ -107,9 +115,7 @@ export default function AdminPage() {
|
|||||||
if (!editUser || !selectedUser) return false;
|
if (!editUser || !selectedUser) return false;
|
||||||
// Compare primitive fields
|
// Compare primitive fields
|
||||||
return (
|
return (
|
||||||
editUser.name !== selectedUser.name ||
|
editUser.name !== selectedUser.name || editUser.role !== selectedUser.role || editUser.password !== selectedUser.password
|
||||||
editUser.role !== selectedUser.role ||
|
|
||||||
editUser.password !== selectedUser.password
|
|
||||||
);
|
);
|
||||||
}, [editUser, selectedUser]);
|
}, [editUser, selectedUser]);
|
||||||
|
|
||||||
@ -117,11 +123,9 @@ export default function AdminPage() {
|
|||||||
const handleUpdate = (e: React.FormEvent) => {
|
const handleUpdate = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!editUser) return;
|
if (!editUser) return;
|
||||||
setUsers(prev =>
|
setUsers((prev) => prev.map((u) => (u.email === editUser.email ? { ...editUser } : u)));
|
||||||
prev.map(u =>
|
// todo create receiving api route
|
||||||
u.email === editUser.email ? { ...editUser } : u
|
// todo send to api route
|
||||||
)
|
|
||||||
);
|
|
||||||
// After successful update, update selectedUser local state
|
// After successful update, update selectedUser local state
|
||||||
// (editUser will auto-sync due to useEffect on users)
|
// (editUser will auto-sync due to useEffect on users)
|
||||||
};
|
};
|
||||||
@ -130,7 +134,7 @@ export default function AdminPage() {
|
|||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (!selectedUser) return;
|
if (!selectedUser) return;
|
||||||
if (!window.confirm(`Are you sure you want to delete "${selectedUser.name}"? This cannot be undone.`)) 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));
|
setUsers((prev) => prev.filter((u) => u.email !== selectedUser.email));
|
||||||
setSelectedEmail(null);
|
setSelectedEmail(null);
|
||||||
setEditUser(null);
|
setEditUser(null);
|
||||||
};
|
};
|
||||||
@ -152,13 +156,13 @@ export default function AdminPage() {
|
|||||||
className="flex-1 border rounded-lg px-2 py-1 text-sm"
|
className="flex-1 border rounded-lg px-2 py-1 text-sm"
|
||||||
placeholder={`Search by ${searchField}`}
|
placeholder={`Search by ${searchField}`}
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={e => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="border rounded-lg px-2 py-1 text-sm bg-white hover:bg-neutral-100 transition font-semibold"
|
className="border rounded-lg px-2 py-1 text-sm bg-white hover:bg-neutral-100 transition font-semibold"
|
||||||
style={{ width: "80px" }} // fixed width, adjust as needed
|
style={{ width: "80px" }} // fixed width, adjust as needed
|
||||||
onClick={() => setSearchField(field => field === "name" ? "email" : "name")}
|
onClick={() => setSearchField((field) => (field === "name" ? "email" : "name"))}
|
||||||
title={`Switch to searching by ${searchField === "name" ? "Email" : "Name"}`}
|
title={`Switch to searching by ${searchField === "name" ? "Email" : "Name"}`}
|
||||||
>
|
>
|
||||||
{searchField === "name" ? "Email" : "Name"}
|
{searchField === "name" ? "Email" : "Name"}
|
||||||
@ -170,28 +174,39 @@ export default function AdminPage() {
|
|||||||
<div className="relative" ref={filterDropdownRef}>
|
<div className="relative" ref={filterDropdownRef}>
|
||||||
<button
|
<button
|
||||||
className={`px-3 py-1 rounded-lg border font-semibold flex items-center transition
|
className={`px-3 py-1 rounded-lg border font-semibold flex items-center transition
|
||||||
${roleFilter !== "all"
|
${
|
||||||
|
roleFilter !== "all"
|
||||||
? "bg-blue-600 text-white border-blue-600 hover:bg-blue-700"
|
? "bg-blue-600 text-white border-blue-600 hover:bg-blue-700"
|
||||||
: "bg-white text-gray-700 border hover:bg-neutral-200"}
|
: "bg-white text-gray-700 border hover:bg-neutral-200"
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
onClick={() => setFilterDropdownOpen(v => !v)}
|
onClick={() => setFilterDropdownOpen((v) => !v)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Filter <svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" /></svg>
|
Filter{" "}
|
||||||
|
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{filterDropdownOpen && (
|
{filterDropdownOpen && (
|
||||||
<div className="absolute z-10 mt-1 left-0 bg-white border rounded-lg shadow-sm w-28 py-1">
|
<div className="absolute z-10 mt-1 left-0 bg-white border rounded-lg shadow-sm w-28 py-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => { setRoleFilter("all"); setFilterDropdownOpen(false); }}
|
onClick={() => {
|
||||||
|
setRoleFilter("all");
|
||||||
|
setFilterDropdownOpen(false);
|
||||||
|
}}
|
||||||
className={`w-full text-left px-3 py-1 hover:bg-blue-50 border-b border-gray-100 last:border-0
|
className={`w-full text-left px-3 py-1 hover:bg-blue-50 border-b border-gray-100 last:border-0
|
||||||
${roleFilter === "all" ? "font-bold text-blue-600" : ""}`}
|
${roleFilter === "all" ? "font-bold text-blue-600" : ""}`}
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
</button>
|
</button>
|
||||||
{allRoles.map(role => (
|
{allRoles.map((role) => (
|
||||||
<button
|
<button
|
||||||
key={role}
|
key={role}
|
||||||
onClick={() => { setRoleFilter(role); setFilterDropdownOpen(false); }}
|
onClick={() => {
|
||||||
|
setRoleFilter(role);
|
||||||
|
setFilterDropdownOpen(false);
|
||||||
|
}}
|
||||||
className={`w-full text-left px-3 py-1 hover:bg-blue-50 border-b border-gray-100 last:border-0
|
className={`w-full text-left px-3 py-1 hover:bg-blue-50 border-b border-gray-100 last:border-0
|
||||||
${roleFilter === role ? "font-bold text-blue-600" : ""}`}
|
${roleFilter === role ? "font-bold text-blue-600" : ""}`}
|
||||||
>
|
>
|
||||||
@ -205,14 +220,17 @@ export default function AdminPage() {
|
|||||||
<div className="relative" ref={sortDropdownRef}>
|
<div className="relative" ref={sortDropdownRef}>
|
||||||
<button
|
<button
|
||||||
className="px-3 py-1 rounded-lg bg-white border text-gray-700 font-semibold flex items-center hover:bg-neutral-200"
|
className="px-3 py-1 rounded-lg bg-white border text-gray-700 font-semibold flex items-center hover:bg-neutral-200"
|
||||||
onClick={() => setSortDropdownOpen(v => !v)}
|
onClick={() => setSortDropdownOpen((v) => !v)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Sort <svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" /></svg>
|
Sort{" "}
|
||||||
|
<svg className="w-4 h-4 ml-1" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{sortDropdownOpen && (
|
{sortDropdownOpen && (
|
||||||
<div className="absolute z-10 mt-1 left-0 bg-white border rounded-lg shadow-sm w-28 ">
|
<div className="absolute z-10 mt-1 left-0 bg-white border rounded-lg shadow-sm w-28 ">
|
||||||
{sortFields.map(opt => (
|
{sortFields.map((opt) => (
|
||||||
<button
|
<button
|
||||||
key={opt.value}
|
key={opt.value}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -231,7 +249,7 @@ export default function AdminPage() {
|
|||||||
{/* Asc/Desc Toggle */}
|
{/* Asc/Desc Toggle */}
|
||||||
<button
|
<button
|
||||||
className="ml-2 px-2 py-1 rounded-lg bg-white border text-gray-700 font-semibold flex items-center hover:bg-neutral-200"
|
className="ml-2 px-2 py-1 rounded-lg bg-white border text-gray-700 font-semibold flex items-center hover:bg-neutral-200"
|
||||||
onClick={() => setSortDir(d => (d === "asc" ? "desc" : "asc"))}
|
onClick={() => setSortDir((d) => (d === "asc" ? "desc" : "asc"))}
|
||||||
title={sortDir === "asc" ? "Ascending" : "Descending"}
|
title={sortDir === "asc" ? "Ascending" : "Descending"}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -261,9 +279,7 @@ export default function AdminPage() {
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{sortedUsers.length === 0 && (
|
{sortedUsers.length === 0 && <li className="text-gray-400 text-center py-6">No users found.</li>}
|
||||||
<li className="text-gray-400 text-center py-6">No users found.</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -282,9 +298,7 @@ export default function AdminPage() {
|
|||||||
<span className="text-sm text-gray-500">{editUser.id}</span>
|
<span className="text-sm text-gray-500">{editUser.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Email (unique):</label>
|
||||||
Email (unique):
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="w-full border px-3 py-2 rounded-lg outline-none bg-gray-100 cursor-not-allowed"
|
className="w-full border px-3 py-2 rounded-lg outline-none bg-gray-100 cursor-not-allowed"
|
||||||
type="email"
|
type="email"
|
||||||
@ -303,9 +317,7 @@ export default function AdminPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Name:</label>
|
||||||
Name:
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
||||||
type="text"
|
type="text"
|
||||||
@ -315,9 +327,7 @@ export default function AdminPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Role:</label>
|
||||||
Role:
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
||||||
name="role"
|
name="role"
|
||||||
@ -325,14 +335,14 @@ export default function AdminPage() {
|
|||||||
onChange={handleEditChange}
|
onChange={handleEditChange}
|
||||||
>
|
>
|
||||||
{allRoles.map((role) => (
|
{allRoles.map((role) => (
|
||||||
<option key={role} value={role}>{roleLabels[role]}</option>
|
<option key={role} value={role}>
|
||||||
|
{roleLabels[role]}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Password:</label>
|
||||||
Password:
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
className="w-full border px-3 py-2 rounded-lg outline-none focus:ring-2 focus:ring-blue-300"
|
||||||
type="text"
|
type="text"
|
||||||
@ -352,7 +362,8 @@ export default function AdminPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className={`px-4 py-2 rounded-lg font-semibold transition
|
className={`px-4 py-2 rounded-lg font-semibold transition
|
||||||
${isEditChanged
|
${
|
||||||
|
isEditChanged
|
||||||
? "bg-blue-600 hover:bg-blue-700 text-white shadow"
|
? "bg-blue-600 hover:bg-blue-700 text-white shadow"
|
||||||
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
||||||
}`}
|
}`}
|
||||||
@ -364,9 +375,7 @@ export default function AdminPage() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center text-gray-400 mt-16 text-lg">
|
<div className="text-center text-gray-400 mt-16 text-lg">Select a user...</div>
|
||||||
Select a user...
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { PrismaClient } from "@prismaclient";
|
import { PrismaClient } from '@prismaclient';
|
||||||
|
|
||||||
const usingPrisma = false;
|
const usingPrisma = false;
|
||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
if (usingPrisma) prisma = new PrismaClient();
|
if (usingPrisma) prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// todo add specification of date range in request
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
const json = await req.json();
|
const json = await req.json();
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { PrismaClient } from "@prismaclient";
|
import { PrismaClient } from '@prismaclient';
|
||||||
|
|
||||||
const usingPrisma = false;
|
const usingPrisma = false;
|
||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
if (usingPrisma) prisma = new PrismaClient();
|
if (usingPrisma) prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// todo remove if (usingPrisma) code
|
||||||
|
// todo add specification of date range in request
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
const events = [
|
const events = [
|
||||||
|
|||||||
@ -11,6 +11,8 @@ const usingPrisma = false;
|
|||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
if (usingPrisma) prisma = new PrismaClient();
|
if (usingPrisma) prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// todo remove if (usingPrisma) code
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
const { email, password, name } = await req.json(); // Parse incoming JSON data
|
const { email, password, name } = await req.json(); // Parse incoming JSON data
|
||||||
@ -18,6 +20,7 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
const userData = await readUserCsv();
|
const userData = await readUserCsv();
|
||||||
|
|
||||||
|
// todo remove console logs
|
||||||
console.log(userData);
|
console.log(userData);
|
||||||
console.log("Name:", name); // ! remove
|
console.log("Name:", name); // ! remove
|
||||||
console.log("Email:", email); // ! remove
|
console.log("Email:", email); // ! remove
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
import { PrismaClient } from "@prismaclient";
|
import { PrismaClient } from '@prismaclient';
|
||||||
import { env } from "@utils/env";
|
import { env } from '@utils/env';
|
||||||
import { verifyJwt } from "@utils/verifyJwt";
|
import { verifyJwt } from '@utils/verifyJwt';
|
||||||
|
|
||||||
const usingPrisma = false;
|
const usingPrisma = false;
|
||||||
let prisma: PrismaClient;
|
let prisma: PrismaClient;
|
||||||
if (usingPrisma) prisma = new PrismaClient();
|
if (usingPrisma) prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// todo remove if (usingPrisma) code
|
||||||
|
// todo add specification of date range in request
|
||||||
|
|
||||||
// Artefact type
|
// Artefact type
|
||||||
interface Artefact {
|
interface Artefact {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import axios, { AxiosError } from 'axios';
|
||||||
import { useStoreActions } from "@hooks/store";
|
import { useRouter } from 'next/navigation';
|
||||||
import axios, { AxiosError } from "axios";
|
import { useEffect, useState } from 'react';
|
||||||
import { useRouter } from "next/navigation";
|
import { FaSignOutAlt } from 'react-icons/fa';
|
||||||
import { User } from "@appTypes/Prisma";
|
import { FaUser } from 'react-icons/fa6';
|
||||||
import { FaSignOutAlt } from "react-icons/fa";
|
|
||||||
import { FaUser } from "react-icons/fa6";
|
import { User } from '@appTypes/Prisma';
|
||||||
|
import { useStoreActions } from '@hooks/store';
|
||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -50,6 +51,8 @@ export default function Profile() {
|
|||||||
}
|
}
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
|
// todo create receiving api route
|
||||||
|
// todo handle sending fields to api route
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
setUserState({ ...user!, name, email, role });
|
setUserState({ ...user!, name, email, role });
|
||||||
alert("Profile updated successfully.");
|
alert("Profile updated successfully.");
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
import Image from 'next/image';
|
||||||
import { Dispatch, SetStateAction, useCallback, useState } from "react";
|
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||||
|
|
||||||
import Artefact from "@appTypes/Artefact";
|
import Artefact from '@appTypes/Artefact';
|
||||||
import { Currency } from "@appTypes/StoreModel";
|
import { Currency } from '@appTypes/StoreModel';
|
||||||
import { useStoreState } from "@hooks/store";
|
import { useStoreState } from '@hooks/store';
|
||||||
|
|
||||||
// Artefacts Data
|
// Artefacts Data
|
||||||
const artefacts: Artefact[] = [
|
const artefacts: Artefact[] = [
|
||||||
@ -322,6 +322,10 @@ export default function Shop() {
|
|||||||
setError("CVC must be 3 or 4 digits.");
|
setError("CVC must be 3 or 4 digits.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// todo create receiving api route
|
||||||
|
// todo handle sending to api route
|
||||||
|
// todo remove order number generation - we don't need one
|
||||||
|
// todo add option to save details in new account
|
||||||
const genOrder = () => "#" + Math.random().toString(36).substring(2, 10).toUpperCase();
|
const genOrder = () => "#" + Math.random().toString(36).substring(2, 10).toUpperCase();
|
||||||
setOrderNumber(genOrder());
|
setOrderNumber(genOrder());
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Dispatch, SetStateAction, useMemo, useState } from "react";
|
import { Dispatch, SetStateAction, useMemo, useState } from 'react';
|
||||||
import { FaTimes } from "react-icons/fa";
|
import { FaTimes } from 'react-icons/fa';
|
||||||
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from "react-icons/fa6";
|
import { FaCalendarPlus, FaCartShopping, FaWarehouse } from 'react-icons/fa6';
|
||||||
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from "react-icons/io5";
|
import { IoFilter, IoFilterCircleOutline, IoFilterOutline, IoToday } from 'react-icons/io5';
|
||||||
|
|
||||||
// import { Artefact } from "@appTypes/Prisma";
|
// import { Artefact } from "@appTypes/Prisma";
|
||||||
|
|
||||||
@ -275,6 +275,8 @@ function LogModal({ onClose }: { onClose: () => void }) {
|
|||||||
}
|
}
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
|
// todo create receiving api route
|
||||||
|
// todo handle sending fields to api route
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
|
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulated API call
|
||||||
alert(`Logged ${name} to storage: ${storageLocation}`);
|
alert(`Logged ${name} to storage: ${storageLocation}`);
|
||||||
onClose();
|
onClose();
|
||||||
@ -406,6 +408,8 @@ function BulkLogModal({ onClose }: { onClose: () => void }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLog = async () => {
|
const handleLog = async () => {
|
||||||
|
// todo create receiving api route
|
||||||
|
// todo handle sending fields to api route
|
||||||
if (!palletNote || !storageLocation) {
|
if (!palletNote || !storageLocation) {
|
||||||
setError("All fields are required.");
|
setError("All fields are required.");
|
||||||
return;
|
return;
|
||||||
@ -504,6 +508,8 @@ function EditModal({ artefact, onClose }: { artefact: Artefact; onClose: () => v
|
|||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
// todo add image display
|
||||||
|
|
||||||
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
const handleOverlayClick = (e: { target: any; currentTarget: any }) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -50,6 +50,7 @@ export default function Sidebar({
|
|||||||
button1Name,
|
button1Name,
|
||||||
button2Name,
|
button2Name,
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
|
// todo add buttons 1 and 2 click handlers
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col h-full w-80 relative bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg`}>
|
<div className={`flex flex-col h-full w-80 relative bg-gradient-to-b from-neutral-100 to-neutral-50 shadow-lg`}>
|
||||||
<div className="py-6">
|
<div className="py-6">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user