From bd7bea8db697f5b025b8d93f86677a9a69cdf2b4 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Tue, 4 Nov 2025 20:16:14 -0600 Subject: [PATCH] feat(leases): added in leases and move table to reuseable component --- .../app/forklifts/companies/folder.bru | 2 +- .../app/forklifts/lease/Get lease.bru | 22 + .../app/forklifts/lease/Update lease.bru | 27 + .../app/forklifts/lease/add lease.bru | 25 + .../app/forklifts/lease/folder.bru | 8 + .../forklifts/routes/companies/addCompany.ts | 11 +- .../forklifts/routes/leases/addLease.ts | 114 + .../forklifts/routes/leases/getLeases.ts | 48 + .../forklifts/routes/leases/leaseRoutes.ts | 18 + .../forklifts/routes/leases/updateLease.ts | 114 + app/src/internal/forklifts/routes/routes.ts | 5 + app/src/pkg/db/schema/forkliftLeases.ts | 19 +- .../src/components/navBar/ForkliftSideBar.tsx | 2 +- frontend/src/components/navBar/Nav.tsx | 24 +- frontend/src/components/ui/button.tsx | 100 +- frontend/src/components/ui/calendar.tsx | 407 ++- frontend/src/components/ui/datePicker.tsx | 44 +- frontend/src/components/ui/dropdown-menu.tsx | 386 ++- .../formStuff/components/CalenderSelect.tsx | 53 + frontend/src/lib/formStuff/index.tsx | 28 +- .../src/lib/querys/forklifts/getLeases.ts | 17 + frontend/src/lib/tableStuff/TableNoExpand.tsx | 112 + .../_app/_forklifts/-components/NewLease.tsx | 128 + .../_app/_forklifts/forklifts/companies.tsx | 108 +- .../_app/_forklifts/forklifts/leases.tsx | 124 + migrations/0037_cold_blur.sql | 4 + migrations/0038_secret_luminals.sql | 1 + migrations/meta/0037_snapshot.json | 2208 ++++++++++++++++ migrations/meta/0038_snapshot.json | 2216 +++++++++++++++++ migrations/meta/_journal.json | 14 + 30 files changed, 5788 insertions(+), 601 deletions(-) create mode 100644 LogisticsSupportTool_API_DOCS/app/forklifts/lease/Get lease.bru create mode 100644 LogisticsSupportTool_API_DOCS/app/forklifts/lease/Update lease.bru create mode 100644 LogisticsSupportTool_API_DOCS/app/forklifts/lease/add lease.bru create mode 100644 LogisticsSupportTool_API_DOCS/app/forklifts/lease/folder.bru create mode 100644 app/src/internal/forklifts/routes/leases/addLease.ts create mode 100644 app/src/internal/forklifts/routes/leases/getLeases.ts create mode 100644 app/src/internal/forklifts/routes/leases/leaseRoutes.ts create mode 100644 app/src/internal/forklifts/routes/leases/updateLease.ts create mode 100644 frontend/src/lib/formStuff/components/CalenderSelect.tsx create mode 100644 frontend/src/lib/querys/forklifts/getLeases.ts create mode 100644 frontend/src/lib/tableStuff/TableNoExpand.tsx create mode 100644 frontend/src/routes/_app/_forklifts/-components/NewLease.tsx create mode 100644 frontend/src/routes/_app/_forklifts/forklifts/leases.tsx create mode 100644 migrations/0037_cold_blur.sql create mode 100644 migrations/0038_secret_luminals.sql create mode 100644 migrations/meta/0037_snapshot.json create mode 100644 migrations/meta/0038_snapshot.json diff --git a/LogisticsSupportTool_API_DOCS/app/forklifts/companies/folder.bru b/LogisticsSupportTool_API_DOCS/app/forklifts/companies/folder.bru index 2a303bb..3375929 100644 --- a/LogisticsSupportTool_API_DOCS/app/forklifts/companies/folder.bru +++ b/LogisticsSupportTool_API_DOCS/app/forklifts/companies/folder.bru @@ -1,6 +1,6 @@ meta { name: companies - seq: 1 + seq: 2 } auth { diff --git a/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Get lease.bru b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Get lease.bru new file mode 100644 index 0000000..17deb0d --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Get lease.bru @@ -0,0 +1,22 @@ +meta { + name: Get lease + type: http + seq: 2 +} + +get { + url: {{url}}/lst/api/forklifts/leases + body: none + auth: inherit +} + +body:json { + { + "name":"Delage DLL" + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Update lease.bru b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Update lease.bru new file mode 100644 index 0000000..fd8727d --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/Update lease.bru @@ -0,0 +1,27 @@ +meta { + name: Update lease + type: http + seq: 3 +} + +patch { + url: {{url}}/lst/api/forklifts/leases/:id + body: json + auth: inherit +} + +params:path { + id: de10c8ee-5756-4efb-9664-3c55338b2b60 +} + +body:json { + { + + "endDate": "3/25/2029" + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/LogisticsSupportTool_API_DOCS/app/forklifts/lease/add lease.bru b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/add lease.bru new file mode 100644 index 0000000..bfcb0c8 --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/add lease.bru @@ -0,0 +1,25 @@ +meta { + name: add lease + type: http + seq: 1 +} + +post { + url: {{url}}/lst/api/forklifts/leases + body: json + auth: inherit +} + +body:json { + { + "leaseNumber":"Delage DLL", + "startDate": "", + "endDate": "", + "companyId": "" + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/LogisticsSupportTool_API_DOCS/app/forklifts/lease/folder.bru b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/folder.bru new file mode 100644 index 0000000..f0911d7 --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/forklifts/lease/folder.bru @@ -0,0 +1,8 @@ +meta { + name: lease + seq: 1 +} + +auth { + mode: inherit +} diff --git a/app/src/internal/forklifts/routes/companies/addCompany.ts b/app/src/internal/forklifts/routes/companies/addCompany.ts index f5903b3..9c6be25 100644 --- a/app/src/internal/forklifts/routes/companies/addCompany.ts +++ b/app/src/internal/forklifts/routes/companies/addCompany.ts @@ -33,7 +33,16 @@ router.post("/", async (req: Request, res: Response) => { upd_user: req.user?.username, upd_date: sql`NOW()`, }) - //.onConflictDoNothing() + .onConflictDoUpdate({ + target: forkliftCompanies.name, + set: { + ...parsed.data, + add_user: req.user?.username, + add_date: sql`NOW()`, + upd_user: req.user?.username, + upd_date: sql`NOW()`, + }, + }) .returning({ name: forkliftCompanies.name, }), diff --git a/app/src/internal/forklifts/routes/leases/addLease.ts b/app/src/internal/forklifts/routes/leases/addLease.ts new file mode 100644 index 0000000..74ba614 --- /dev/null +++ b/app/src/internal/forklifts/routes/leases/addLease.ts @@ -0,0 +1,114 @@ +import axios from "axios"; +import { type DrizzleError, sql } from "drizzle-orm"; +import type { Request, Response } from "express"; +import { Router } from "express"; +import https from "https"; +import { db } from "../../../../pkg/db/db.js"; +import { + insertLeasesCompanySchema, + leases, +} from "../../../../pkg/db/schema/forkliftLeases.js"; +import { createLogger } from "../../../../pkg/logger/logger.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; + +const router = Router(); + +router.post("/", async (req: Request, res: Response) => { + // when a new server is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there + //res.status(200).json({ message: "Server added", ip: req.hostname }); + const log = createLogger({ module: "forklift", subModule: "add lease" }); + const parsed = insertLeasesCompanySchema.safeParse(req.body); + + if (!parsed.success) { + return res.status(400).json({ errors: parsed.error.flatten() }); + } + + const { data, error } = await tryCatch( + db + .insert(leases) + .values({ + ...parsed.data, + add_user: req.user?.username, + add_date: sql`NOW()`, + upd_user: req.user?.username, + upd_date: sql`NOW()`, + }) + .onConflictDoUpdate({ + target: leases.leaseNumber, + set: { + ...parsed.data, + add_user: req.user?.username, + add_date: sql`NOW()`, + upd_user: req.user?.username, + upd_date: sql`NOW()`, + }, + }) + .returning({ + leaseNumber: leases.leaseNumber, + }), + ); + + if (error) { + const err: DrizzleError = error; + return res.status(400).json({ + message: `Error adding lease`, + error: err.cause, + }); + } + + if (req.hostname === "localhost" && process.env.MAIN_SERVER) { + log.info({}, "Running in dev server about to add in a new server"); + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + baseURL: process.env.MAIN_SERVER, // e.g. "https://example.com" + withCredentials: true, + }); + + const loginRes = (await axiosInstance.post( + `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, + { + username: process.env.MAIN_SERVER_USERNAME, + password: process.env.MAIN_SERVER_PASSWORD, + }, + { + headers: { "Content-Type": "application/json" }, + }, + )) as any; + const setCookie = loginRes.headers["set-cookie"][0]; + + if (!setCookie) { + throw new Error("Did not receive a Set-Cookie header from login"); + } + + const { data, error } = await tryCatch( + axios.post( + `${process.env.MAIN_SERVER}/lst/api/forklifts/leases`, + parsed.data, + { + headers: { + "Content-Type": "application/json", + Cookie: setCookie.split(";")[0], + }, + withCredentials: true, + }, + ), + ); + + if (error) { + log.error( + { stack: error }, + "There was an error adding the company to Main Server", + ); + } + log.info( + { stack: data?.data }, + "A new Company was just added to the server.", + ); + } + + return res + .status(201) + .json({ message: `lease ${data[0]?.leaseNumber} added`, data: data }); +}); + +export default router; diff --git a/app/src/internal/forklifts/routes/leases/getLeases.ts b/app/src/internal/forklifts/routes/leases/getLeases.ts new file mode 100644 index 0000000..7be56d9 --- /dev/null +++ b/app/src/internal/forklifts/routes/leases/getLeases.ts @@ -0,0 +1,48 @@ +import { and, asc, eq } from "drizzle-orm"; +import type { Request, Response } from "express"; +import { Router } from "express"; +import { db } from "../../../../pkg/db/db.js"; +import { forkliftCompanies } from "../../../../pkg/db/schema/forkliftLeaseCompanys.js"; +import { leases } from "../../../../pkg/db/schema/forkliftLeases.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; + +const router = Router(); + +router.get("/", async (req: Request, res: Response) => { + const lease = req.query.lease; + + const conditions = []; + + if (lease !== undefined) { + conditions.push(eq(leases.leaseNumber, `${lease}`)); + } + + //conditions.push(eq(forkliftCompanies.active, true)); + + const { data, error } = await tryCatch( + db + .select({ + id: leases.id, + leaseNumber: leases.leaseNumber, + startDate: leases.startDate, + endDate: leases.endDate, + leaseLink: leases.leaseLink, + companyName: forkliftCompanies.name, + add_user: leases.add_user, + add_date: leases.add_date, + upd_user: leases.upd_user, + upd_date: leases.upd_date, + }) + .from(leases) + .innerJoin(forkliftCompanies, eq(forkliftCompanies.id, leases.companyId)) + .where(and(...conditions)) + .orderBy(asc(leases.leaseNumber)), + ); + + if (error) { + return res.status(400).json({ error: error }); + } + res.status(200).json({ message: "Current Leases", data: data }); +}); + +export default router; diff --git a/app/src/internal/forklifts/routes/leases/leaseRoutes.ts b/app/src/internal/forklifts/routes/leases/leaseRoutes.ts new file mode 100644 index 0000000..a964539 --- /dev/null +++ b/app/src/internal/forklifts/routes/leases/leaseRoutes.ts @@ -0,0 +1,18 @@ +import { Router } from "express"; +import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js"; + +import addLeases from "./addLease.js"; +import getLeases from "./getLeases.js"; +import updateLeases from "./updateLease.js"; + +const router = Router(); + +router.use("/", requireAuth("forklifts", ["systemAdmin", "admin"]), getLeases); +router.use("/", requireAuth("forklifts", ["systemAdmin", "admin"]), addLeases); +router.use( + "/", + requireAuth("forklifts", ["systemAdmin", "admin"]), + updateLeases, +); + +export default router; diff --git a/app/src/internal/forklifts/routes/leases/updateLease.ts b/app/src/internal/forklifts/routes/leases/updateLease.ts new file mode 100644 index 0000000..4bf7a9f --- /dev/null +++ b/app/src/internal/forklifts/routes/leases/updateLease.ts @@ -0,0 +1,114 @@ +import axios from "axios"; +import { eq, sql } from "drizzle-orm"; +import type { Request, Response } from "express"; +import { Router } from "express"; +import https from "https"; +import { db } from "../../../../pkg/db/db.js"; +import { forkliftCompanies } from "../../../../pkg/db/schema/forkliftLeaseCompanys.js"; +import { leases } from "../../../../pkg/db/schema/forkliftLeases.js"; +import { serverData } from "../../../../pkg/db/schema/servers.js"; +import { createLogger } from "../../../../pkg/logger/logger.js"; +import { tryCatch } from "../../../../pkg/utils/tryCatch.js"; + +const router = Router(); + +router.patch("/:id", async (req: Request, res: Response) => { + const log = createLogger({ + module: "forklifts", + subModule: "update leases", + }); + + // when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict. + const id = req.params.id; + const updates: Record = {}; + console.log(req.body); + if (req.body?.leaseNumber !== undefined) { + updates.leaseNumber = req.body.leaseNumber; + } + + if (req.body?.startDate !== undefined) { + updates.startDate = req.body.startDate; + } + + if (req.body?.endDate !== undefined) { + updates.endDate = req.body.endDate; + } + + if (req.body?.companyId !== undefined) { + updates.companyId = req.body.companyId; + } + + if (req.body?.leaseLink !== undefined) { + updates.leaseLink = req.body.leaseLink; + } + + updates.upd_user = req.user!.username || "lst_user"; + updates.upd_date = sql`NOW()`; + + console.log(updates); + try { + if (Object.keys(updates).length > 0) { + await db.update(leases).set(updates).where(eq(leases.id, id)); + } + + if (req.hostname === "localhost" && process.env.MAIN_SERVER) { + log.info({}, "Running in dev server about to add in a new server"); + const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + baseURL: process.env.MAIN_SERVER, + withCredentials: true, + }); + + const loginRes = (await axiosInstance.post( + `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`, + { + username: process.env.MAIN_SERVER_USERNAME, + password: process.env.MAIN_SERVER_PASSWORD, + }, + { + headers: { "Content-Type": "application/json" }, + }, + )) as any; + + const setCookie = loginRes?.headers["set-cookie"][0]; + + //console.log(setCookie.split(";")[0].replace("__Secure-", "")); + + if (!setCookie) { + throw new Error("Did not receive a Set-Cookie header from login"); + } + + const { data, error } = await tryCatch( + axios.patch( + `${process.env.MAIN_SERVER}/lst/api/forklifts/leases/${id}`, + updates, + { + headers: { + "Content-Type": "application/json", + Cookie: setCookie.split(";")[0], + }, + withCredentials: true, + }, + ), + ); + + if (error) { + //console.log(error); + log.error( + { stack: error }, + "There was an error updating the lease to Main Server", + ); + } + log.info( + { stack: data?.data }, + "A new lease was just updated to the server.", + ); + } + res.status(200).json({ message: `${id} was just updated` }); + } catch (error) { + //console.log(error); + res.status(200).json({ message: "Error updating lease", error }); + } +}); + +export default router; diff --git a/app/src/internal/forklifts/routes/routes.ts b/app/src/internal/forklifts/routes/routes.ts index b0cbb75..20ed6e7 100644 --- a/app/src/internal/forklifts/routes/routes.ts +++ b/app/src/internal/forklifts/routes/routes.ts @@ -1,9 +1,14 @@ import type { Express, Request, Response } from "express"; import { requireAuth } from "../../../pkg/middleware/authMiddleware.js"; import companies from "./companies/companiesRoutes.js"; +import leases from "./leases/leaseRoutes.js"; export const setupForkliftRoutes = (app: Express, basePath: string) => { app.use( basePath + "/api/forklifts/companies", // will pass bc system admin but this is just telling us we need this companies, ); + app.use( + basePath + "/api/forklifts/leases", // will pass bc system admin but this is just telling us we need this + leases, + ); }; diff --git a/app/src/pkg/db/schema/forkliftLeases.ts b/app/src/pkg/db/schema/forkliftLeases.ts index c616c89..a9ba40c 100644 --- a/app/src/pkg/db/schema/forkliftLeases.ts +++ b/app/src/pkg/db/schema/forkliftLeases.ts @@ -1,13 +1,26 @@ -import { date, pgTable, text, uuid } from "drizzle-orm/pg-core"; -import { createSelectSchema } from "drizzle-zod"; +import { date, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import z from "zod"; import { forkliftCompanies } from "./forkliftLeaseCompanys.js"; export const leases = pgTable("leases", { id: uuid("id").defaultRandom().primaryKey(), - leaseNumber: text("lease_number").notNull(), + leaseNumber: text("lease_number").unique().notNull(), companyId: uuid("company_id").references(() => forkliftCompanies.id), startDate: date("start_date"), endDate: date("end_date"), leaseLink: text("lease_link"), + add_date: timestamp("add_date").defaultNow(), + add_user: text("add_user").default("LST"), + upd_date: timestamp("upd_date").defaultNow(), + upd_user: text("upd_user").default("LST"), }); export const selectLeasesDataSchema = createSelectSchema(leases); + +export const insertLeasesCompanySchema = createInsertSchema(leases).extend({ + leaseNumber: z.string().min(3), + // zipcode: z + // .string() + // .regex(/^\d{5}$/) + // .optional(), +}); diff --git a/frontend/src/components/navBar/ForkliftSideBar.tsx b/frontend/src/components/navBar/ForkliftSideBar.tsx index c494a95..d409c09 100644 --- a/frontend/src/components/navBar/ForkliftSideBar.tsx +++ b/frontend/src/components/navBar/ForkliftSideBar.tsx @@ -29,7 +29,7 @@ export default function ForkliftSideBar() { }, { title: "Leases", - url: "/lst/app/admin/settings", + url: "/lst/app/forklifts/leases", icon: ReceiptText, role: ["systemAdmin", "admin"], module: "forklifts", diff --git a/frontend/src/components/navBar/Nav.tsx b/frontend/src/components/navBar/Nav.tsx index 60f5648..eb76695 100644 --- a/frontend/src/components/navBar/Nav.tsx +++ b/frontend/src/components/navBar/Nav.tsx @@ -1,6 +1,7 @@ import { Link, useRouterState } from "@tanstack/react-router"; import { useState } from "react"; import NewCompanyForm from "@/routes/_app/_forklifts/-components/NewCompany"; +import NewLeaseForm from "@/routes/_app/_forklifts/-components/NewLease"; import { useAuth, useLogout } from "../../lib/authClient"; import { ModeToggle } from "../mode-toggle"; import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; @@ -21,6 +22,7 @@ export default function Nav() { const router = useRouterState(); const currentPath = router.location.href; const [openDialog, setOpenDialog] = useState(false); + const [openLeaseDialog, setOpenLeaseDialog] = useState(false); return (