Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions labman/prisma/migrations/20260309105825_add_statuses/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- AlterTable
ALTER TABLE "Borrower" ADD COLUMN "status" TEXT;

-- AlterTable
ALTER TABLE "Equipment" ADD COLUMN "status" TEXT;

-- AlterTable
ALTER TABLE "Item" ALTER COLUMN "status" DROP NOT NULL;

-- AlterTable
ALTER TABLE "Loan" ALTER COLUMN "status" DROP NOT NULL;

-- AlterTable
ALTER TABLE "User" ADD COLUMN "status" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Warnings:

- Made the column `status` on table `Borrower` required. This step will fail if there are existing NULL values in that column.
- Made the column `status` on table `Equipment` required. This step will fail if there are existing NULL values in that column.
- Made the column `status` on table `Item` required. This step will fail if there are existing NULL values in that column.
- Made the column `status` on table `Loan` required. This step will fail if there are existing NULL values in that column.

*/
UPDATE "Borrower" SET "status" = 'Active' WHERE "status" IS NULL;
UPDATE "Equipment" SET "status" = 'Active' WHERE "status" IS NULL;

-- AlterTable
ALTER TABLE "Borrower" ALTER COLUMN "status" SET NOT NULL;

-- AlterTable
ALTER TABLE "Equipment" ALTER COLUMN "status" SET NOT NULL;

-- AlterTable
ALTER TABLE "Item" ALTER COLUMN "status" SET NOT NULL;

-- AlterTable
ALTER TABLE "Loan" ALTER COLUMN "status" SET NOT NULL;
3 changes: 3 additions & 0 deletions labman/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ model Equipment {
name String @unique
image String?
createdAt DateTime @default(now())
status String
items Item[]
category EquipmentCategory @relation(fields: [categoryId], references: [id])
}
Expand Down Expand Up @@ -60,6 +61,7 @@ model User {
username String @unique
createdAt DateTime @default(now())
latestActivity DateTime @default(now())
status String?
sessions Session[]
loans Loan[]
}
Expand All @@ -70,6 +72,7 @@ model Borrower {
phone String? @unique
email String? @unique
note String?
status String
creationDate DateTime @default(now())
loans Loan[]
}
Expand Down
2 changes: 1 addition & 1 deletion labman/src/app/(main)/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ html {
}

.item-view {
@apply bg-brand-950 rounded-md p-3 h-180 overflow-y-auto
@apply bg-brand-950 rounded-md p-3 h-149 overflow-y-auto
}

.side-form-label {
Expand Down
27 changes: 14 additions & 13 deletions labman/src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NavBar from "@/components/core/NavBar";
//import {PopupProvider} from "./popupProvider"
import {getUser} from "@/lib/actions";
import SideBar from "@/components/core/SideBar";
import {SideViewProvider} from "@/app/sideViewContext";

const spartan = League_Spartan({
subsets: ["latin"],
Expand All @@ -27,20 +28,20 @@ export default async function RootLayout({

return (
<html lang="en" className={spartan.variable}>

<body className="font-spartan h-screen">

<div className="flex h-screen">

<aside className="w-73 h-screen">
< SideBar />
</aside>

<main className="flex-1 overflow-auto">
<NavBar username={user?.username ?? "Unknown"} />
{children}
</main>
</div>
<SideViewProvider initialType="">
<div className="flex h-screen">

<aside className="w-73 h-screen">
< SideBar />
</aside>

<main className="flex-1 overflow-auto">
<NavBar username={user?.username ?? "Unknown"} />
{children}
</main>
</div>
</SideViewProvider>
</body>
</html>
);
Expand Down
17 changes: 14 additions & 3 deletions labman/src/app/(main)/loans/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@ export default async function Loans() {
}
const loans = await prisma.loan.findMany({
include: {
borrower: true,
item: {
include: {
equipment: true
equipment: {
include: {
category: true,
items: {
include: {
loans: true,
activeLoan: true
}
}

}
}
}
},
borrower: true
}
}
});

Expand Down
22 changes: 0 additions & 22 deletions labman/src/app/(main)/popupProvider.tsx

This file was deleted.

1 change: 1 addition & 0 deletions labman/src/app/api/equipment/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function POST(req: Request) {
name,
image,
categoryId,
status: "Active",
items: {
create: {
status: "Available"
Expand Down
26 changes: 26 additions & 0 deletions labman/src/app/sideViewContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import React, { createContext, useContext, useState } from "react";

type SideViewCtx = {
sideView: string;
setSideView: React.Dispatch<React.SetStateAction<string>>;
};

const SideViewContext = createContext<SideViewCtx | null>(null);

export function SideViewProvider({ children, initialType = "",}: { children: React.ReactNode; initialType?: string; }) {
const [sideView, setSideView] = useState(initialType);

return (
<SideViewContext.Provider value={{ sideView, setSideView }}>
{children}
</SideViewContext.Provider>
);
}

export function useSideView() {
const ctx = useContext(SideViewContext);
if (!ctx) throw new Error("useString must be used within <SideViewProvider>");
return ctx;
}
18 changes: 12 additions & 6 deletions labman/src/components/core/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import {LoanClass} from "@/types/Loan";
interface CardProps {
user?: UserClass
loan?: LoanClass;
setSideView?: (view: string) => void;
setSelectedLoanId?: (id: number | null) => void;
}

// TODO: Could have a button to reactivate a loan, not a priority right now

export default function Card({ loan, user }: CardProps) {
export default function Card({ loan, user, setSelectedLoanId, setSideView }: CardProps) {

let {name, start, last} = {name: "", start: "", last: ""};

Expand All @@ -31,9 +33,9 @@ export default function Card({ loan, user }: CardProps) {
}

return(
<div className="bg-brand-950 border-white border-[1px] rounded-[18px] w-fit">
<div className="bg-brand-950 border-white border-[1px] rounded-[18px] ">
<div className="border-b-white border-b-[1px] flex gap-2 ">
<h1 className="text-3xl font-bold pl-3 pt-2.5">{name}</h1>
<h1 className="text-3xl font-bold pl-3 pt-2.5 text-nowrap">{name}</h1>
{loan && <span className={"mt-3 text-3xl"}>|</span>}
{loan && <p title={loan.item.equipment.name} className="mt-4 text-2xl w-40 whitespace-nowrap overflow-hidden text-ellipsis">Unit {loan.item?.id}</p>}
{loan && <div className={"mt-4 mr-3 ml-auto rounded-md flex justify-center px-1 w-fit h-5 left-3" + (loan.status === "Returned" ? " bg-green-400" : loan.status === "Active" ? " bg-yellow-400" : " bg-red-600" )}>
Expand Down Expand Up @@ -67,11 +69,15 @@ export default function Card({ loan, user }: CardProps) {
</div>}

<div className="mb-3 ml-4 mt-5 flex gap-2">
<button className="button bg-blue-600">Edit</button>
<button onClick={() => loan ? loan.delete() : user ? user.delete() : alert("Error")} className="button bg-red-600">Delete</button>
<button className="button bg-blue-600" onClick={() => {
if (setSideView && setSelectedLoanId && loan) {
setSideView("loanEdit");
setSelectedLoanId(loan.id);
}
}}>Edit</button>
<button onClick={() => loan ? loan.delete() : user ? user.delete() : alert("Error")} className="button bg-red-600">{user && user.status === "deleting" ? "Deleting..." : "Delete" }</button>
{ loan && loan.status != "Returned" && <button onClick={() => loan.return()} className="button bg-green-500 ml-auto mr-3">Return</button>}
</div>

</div>
)
}
79 changes: 40 additions & 39 deletions labman/src/components/core/CardList.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,13 @@
"use client"
import Card from "@/components/core/Card";
import {useState} from "react";
import {useState, useOptimistic, startTransition} from "react";
import {User} from "@/generated/prisma";
import {UserClass} from "@/types/User";
import {returnLoan, deleteLoan, deleteUser} from "@/lib/actions";
import {LoanClass} from "@/types/Loan";


type Loans = {
id: number;
startDate: Date;
endDate: Date;
status: string;

borrower: {
id: number;
name: string;
phone?: string | null;
email?: string | null
note?: string | null
creationDate: Date;

}
item: {
id: number;
equipment: {
id: number;
name: string;
categoryId: number;
image: string | null;
createdAt: Date;

}
}
}[];

type Loan = Loans[0];
import EditLoan from "@/components/loans/EditLoan";
import {useSideView} from "@/app/sideViewContext";
import {Loan} from "@/types/Loan";

interface CardListProps {
loansProp?: Loan[];
Expand All @@ -47,10 +19,20 @@ export default function CardList({ loansProp = [], usersProp = []}: CardListProp

const [loans, setLoans] = useState<Loan[]>(loansProp);
const [users, setUsers] = useState<User[]>(usersProp);

const [optimisticUsers, removeUser] = useOptimistic(
users,
(currentUsers, idToRemove : number) =>
currentUsers.map(user =>
user.id === idToRemove ? { ...user, status: "deleting" } : user))
// TODO: Temporary use of password field until I add another alternative
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");

const [selectedLoanId, setSelectedLoanId] = useState<number | null>(null);

const { sideView, setSideView } = useSideView();


async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/register", {
Expand All @@ -67,7 +49,6 @@ export default function CardList({ loansProp = [], usersProp = []}: CardListProp
const newUser : User = await res.json();

if (newUser) {
console.log(newUser)
setUsername("");
setPassword("");
setUsers(prev => [...prev, newUser]);
Expand All @@ -78,14 +59,28 @@ export default function CardList({ loansProp = [], usersProp = []}: CardListProp
}
}

async function deleteAction(id: number) {
const res = await deleteUser(id);
if (res.type === "error") return alert(res.message);
setUsers(prev => prev.filter(user => user.id !== id));
}

async function handleDeleteUser(userId: number) {
if (window.confirm("Are you sure you want to delete this user?")) {
setUsers(prev => prev.filter(user => user.id !== userId));
await deleteUser(userId);
// Optimistically remove the user from the UI
startTransition(async () => {
removeUser(userId)
try {
await deleteAction(userId);
} catch (e) {
alert("Failed to delete user: " + e);
}
})
}
}



const hasReturnedLoans = loans.some(
(loan) => loan.status === "Returned"
)
Expand Down Expand Up @@ -118,6 +113,11 @@ export default function CardList({ loansProp = [], usersProp = []}: CardListProp

return (
<div className={"ml-5 mt-5"}>
{ sideView == "loanEdit" && selectedLoanId && <EditLoan
loan={loans.find(loan => loan.id === selectedLoanId)!}
setLoans={setLoans}

/>}
{ users.length !== 0 && <div className={"mb-15"}>
<form onSubmit={handleSubmit}>
<input value={username} onChange={(e) => setUsername(e.target.value)} type="text" name="username" placeholder="Username" className="bg-white rounded-md p-2 m-2 placeholder-black text-black" />
Expand All @@ -140,17 +140,18 @@ export default function CardList({ loansProp = [], usersProp = []}: CardListProp
returnLoan: async (id : number) => handleReturnLoan(id)
}
)
return <Card loan={loan} key={loan.id} />;
return <Card loan={loan} setSelectedLoanId={setSelectedLoanId} setSideView={setSideView} key={loan.id} />;
})}
{users.map(userDto => {
{optimisticUsers.map(userDto => {
const user = new UserClass(
userDto.id,
userDto.username,
userDto.createdAt,
userDto.latestActivity,
{
deleteUser: async (id: number) => handleDeleteUser(id)
}
},
userDto.status
)
return <Card user={user} key={user.id} />;
})}
Expand Down
1 change: 0 additions & 1 deletion labman/src/components/core/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use client"
import Button from "@/components/core/Button";
import PathName from "@/components/core/PathName";
import {logout} from "@/lib/actions";

Expand Down
Loading
Loading