Compare commits
5 Commits
b1c56ee4bb
...
6ce4d84fd0
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ce4d84fd0 | |||
| 2e05f6eeee | |||
| 577584ef4d | |||
| 360c0163f1 | |||
| bd7bea8db6 |
@@ -1,33 +1,33 @@
|
|||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: Report a bug to help us improve.
|
about: Report a bug to help us improve.
|
||||||
title: "[BUG] "
|
title: "[BUG] "
|
||||||
labels: ["bug", "needs-triage"]
|
labels: ["bug", "needs-triage"]
|
||||||
---
|
---
|
||||||
|
|
||||||
### Describe the bug
|
### Describe the bug
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
### Steps to reproduce
|
### Steps to reproduce
|
||||||
|
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
### Expected behavior
|
### Expected behavior
|
||||||
|
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
### Gitea Version
|
### Gitea Version
|
||||||
|
|
||||||
e.g., 1.25.0
|
e.g., 1.25.0
|
||||||
|
|
||||||
### Additional context
|
### Additional context
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Invoices
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{url}}/lst/api/forklifts/invoices
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name":"Delage DLL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Update lease
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
patch {
|
||||||
|
url: {{url}}/lst/api/forklifts/invoices/:id
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:path {
|
||||||
|
id: de10c8ee-5756-4efb-9664-3c55338b2b60
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
|
||||||
|
"companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
meta {
|
||||||
|
name: add lease
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{url}}/lst/api/forklifts/invoices
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"leaseId": "0147d082-aee0-4594-b0f4-c6f4ee777e92",
|
||||||
|
"invoiceNumber": "592596987",
|
||||||
|
"invoiceDate": "10/12/2025",
|
||||||
|
"uploadedBy": "matthes01",
|
||||||
|
"totalAmount": "1820.88",
|
||||||
|
"forklifts": [
|
||||||
|
{ "forklift_Id": "ec2f3759-1580-4c1b-8fbf-8a4b0b506758", "amount": 909.91 }
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: Invoices
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
meta {
|
meta {
|
||||||
name: companies
|
name: companies
|
||||||
seq: 1
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Get forklift
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{url}}/lst/api/forklifts
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name":"Delage DLL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
meta {
|
||||||
|
name: Update forklfit
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
patch {
|
||||||
|
url: {{url}}/lst/api/forklifts/:id
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:path {
|
||||||
|
id: ec2f3759-1580-4c1b-8fbf-8a4b0b506758
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
|
||||||
|
"glCode": 31
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
meta {
|
||||||
|
name: add forklift
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{url}}/lst/api/forklifts
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"serialNumber":"FN682004",
|
||||||
|
"model": "EFG220",
|
||||||
|
"plant": "Iowa City ISBM",
|
||||||
|
"glCode": 31,
|
||||||
|
"profitCenter": 30,
|
||||||
|
"manufacturer":"Jungheinrich",
|
||||||
|
"manufacturerYear":"2022",
|
||||||
|
"engine":"electric",
|
||||||
|
"batteryType":"lead acid",
|
||||||
|
"leaseId":"0147d082-aee0-4594-b0f4-c6f4ee777e92"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: forklifts
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
{
|
||||||
|
|
||||||
|
"companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -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":"500-50489192",
|
||||||
|
"startDate": "11/08/2023",
|
||||||
|
"endDate": "11/12/2025",
|
||||||
|
"companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: lease
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
@@ -13,12 +13,12 @@ post {
|
|||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"scannerId": 999,
|
"scannerId": 999,
|
||||||
"lotNr": 3314,
|
"lotNr": 26656,
|
||||||
"machineId": 22, // 457=22, 458=23
|
"machineId": 5, // 457=22, 458=23
|
||||||
"printerId": 22, // 457=22, 458=23
|
"printerId": 7, // 457=22, 458=23
|
||||||
"layoutId": 7,
|
"layoutId": 22,
|
||||||
"numberOfCopies": 0,
|
"numberOfCopies": 1,
|
||||||
"qtyToPrint": 1
|
"qtyToPrint":0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,16 @@ router.post("/", async (req: Request, res: Response) => {
|
|||||||
upd_user: req.user?.username,
|
upd_user: req.user?.username,
|
||||||
upd_date: sql`NOW()`,
|
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({
|
.returning({
|
||||||
name: forkliftCompanies.name,
|
name: forkliftCompanies.name,
|
||||||
}),
|
}),
|
||||||
|
|||||||
117
app/src/internal/forklifts/routes/forklifts/addForklift.ts
Normal file
117
app/src/internal/forklifts/routes/forklifts/addForklift.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
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 {
|
||||||
|
forklifts,
|
||||||
|
newForkliftsSchema,
|
||||||
|
} from "../../../../pkg/db/schema/forklifts.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 forklift" });
|
||||||
|
const parsed = newForkliftsSchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
return res.status(400).json({ errors: parsed.error.flatten() });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.insert(forklifts)
|
||||||
|
.values({
|
||||||
|
...parsed.data,
|
||||||
|
add_user: req.user?.username,
|
||||||
|
add_date: sql`NOW()`,
|
||||||
|
upd_user: req.user?.username,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: forklifts.serialNumber,
|
||||||
|
set: {
|
||||||
|
...parsed.data,
|
||||||
|
add_user: req.user?.username,
|
||||||
|
add_date: sql`NOW()`,
|
||||||
|
upd_user: req.user?.username,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
serialNumber: forklifts.serialNumber,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
const err: DrizzleError = error;
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Error adding forklift`,
|
||||||
|
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/companies`,
|
||||||
|
// 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: `Forklift ${data[0]?.serialNumber} added`, data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
|
||||||
|
|
||||||
|
import addForklift from "./addForklift.js";
|
||||||
|
import getForklifts from "./getForklifts.js";
|
||||||
|
import updateForklift from "./updateForklift.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin", "manager", "supervisor"]),
|
||||||
|
getForklifts,
|
||||||
|
);
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin"]),
|
||||||
|
addForklift,
|
||||||
|
);
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin"]),
|
||||||
|
updateForklift,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
38
app/src/internal/forklifts/routes/forklifts/getForklifts.ts
Normal file
38
app/src/internal/forklifts/routes/forklifts/getForklifts.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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 { forklifts } from "../../../../pkg/db/schema/forklifts.js";
|
||||||
|
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get("/", async (req: Request, res: Response) => {
|
||||||
|
const plant = req.query.plant;
|
||||||
|
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (plant !== undefined) {
|
||||||
|
conditions.push(eq(leases.leaseNumber, `${plant}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
//conditions.push(eq(forkliftCompanies.active, true));
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(forklifts)
|
||||||
|
//.innerJoin(forkliftCompanies, eq(forkliftCompanies.id, leases.companyId))
|
||||||
|
.where(and(...conditions))
|
||||||
|
.orderBy(asc(forklifts.serialNumber)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).json({ error: error });
|
||||||
|
}
|
||||||
|
res.status(200).json({ message: "Current Forklifts", data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
136
app/src/internal/forklifts/routes/forklifts/updateForklift.ts
Normal file
136
app/src/internal/forklifts/routes/forklifts/updateForklift.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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 { forklifts } from "../../../../pkg/db/schema/forklifts.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<string, any> = {};
|
||||||
|
if (req.body?.model !== undefined) {
|
||||||
|
updates.model = req.body.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.plant !== undefined) {
|
||||||
|
updates.plant = req.body.plant;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.glCode !== undefined) {
|
||||||
|
updates.glCode = req.body.glCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.profitCenter !== undefined) {
|
||||||
|
updates.profitCenter = req.body.profitCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.manufacturer !== undefined) {
|
||||||
|
updates.manufacturer = req.body.manufacturer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.manufacturerYear !== undefined) {
|
||||||
|
updates.manufacturerYear = req.body.manufacturerYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.engine !== undefined) {
|
||||||
|
updates.engine = req.body.engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.batteryType !== undefined) {
|
||||||
|
updates.batteryType = req.body.batteryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.dataPlate !== undefined) {
|
||||||
|
updates.dataPlate = req.body.dataPlate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.forkliftNumber !== undefined) {
|
||||||
|
updates.forkliftNumber = req.body.forkliftNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.upd_user = req.user!.username || "lst_user";
|
||||||
|
updates.upd_date = sql`NOW()`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
await db
|
||||||
|
.update(forklifts)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(forklifts.forklift_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/companies/${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;
|
||||||
160
app/src/internal/forklifts/routes/invoices/addInvoice.ts
Normal file
160
app/src/internal/forklifts/routes/invoices/addInvoice.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
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 type z from "zod";
|
||||||
|
import { db } from "../../../../pkg/db/db.js";
|
||||||
|
import {
|
||||||
|
leaseInvoiceForklifts,
|
||||||
|
newForkliftInvoiceSchema,
|
||||||
|
} from "../../../../pkg/db/schema/forkliftLeasesInvoice.js";
|
||||||
|
import {
|
||||||
|
leaseInvoices,
|
||||||
|
newInvoiceSchema,
|
||||||
|
} from "../../../../pkg/db/schema/leaseInvoices.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 invoice" });
|
||||||
|
|
||||||
|
const parsedInvoice = newInvoiceSchema.safeParse({
|
||||||
|
leaseId: req.body.leaseId,
|
||||||
|
companyId: req.body.companyId,
|
||||||
|
invoiceNumber: req.body.invoiceNumber,
|
||||||
|
invoiceDate: req.body.invoiceDate,
|
||||||
|
uploadedBy: req.body.uploadedBy,
|
||||||
|
totalAmount: req.body.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parsedInvoice.success)
|
||||||
|
return res.status(400).json({ error: parsedInvoice.error.flatten });
|
||||||
|
|
||||||
|
const invoiceData = parsedInvoice.data;
|
||||||
|
|
||||||
|
const forkliftItems = Array.isArray(req.body.forklifts)
|
||||||
|
? req.body.forklifts
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const validatedForklifts = []; //z.infer<typeof newForkliftInvoiceSchema>[] = [];
|
||||||
|
|
||||||
|
for (const item of forkliftItems) {
|
||||||
|
// const parsedItem = newForkliftInvoiceSchema.safeParse(item);
|
||||||
|
// if (parsedItem.success) {
|
||||||
|
validatedForklifts.push(item);
|
||||||
|
//} else {
|
||||||
|
//return res.status(400).json({ error: parsedItem.error.flatten() });
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will be the total invoice amount minus each forklift this way we can keep the total amount in here plus forklifts seperated
|
||||||
|
const totalAmount = (
|
||||||
|
validatedForklifts.reduce((sum, f) => sum + Number(f.amount || 0), 0) -
|
||||||
|
req.body.totalInvoice
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.insert(leaseInvoices)
|
||||||
|
.values({
|
||||||
|
...invoiceData,
|
||||||
|
uploadedBy: req.user!.username || "lst_user",
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: leaseInvoices.invoiceNumber,
|
||||||
|
set: {
|
||||||
|
totalAmount,
|
||||||
|
invoiceDate: invoiceData.invoiceDate,
|
||||||
|
uploadedBy: req.user!.username || "lst_user",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.returning(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const err: DrizzleError = error;
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Error adding lease`,
|
||||||
|
error: err.cause,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceId = data[0]?.id;
|
||||||
|
|
||||||
|
const forkliftInvoices = validatedForklifts.map((f) => ({
|
||||||
|
invoiceId,
|
||||||
|
forkliftId: f.forklift_Id,
|
||||||
|
amount: f.amount,
|
||||||
|
}));
|
||||||
|
console.log(forkliftInvoices);
|
||||||
|
if (validatedForklifts.length > 0) {
|
||||||
|
await db.insert(leaseInvoiceForklifts).values(forkliftInvoices);
|
||||||
|
// .onConflictDoUpdate({
|
||||||
|
// target: [
|
||||||
|
// leaseInvoiceForklifts.invoiceId,
|
||||||
|
// leaseInvoiceForklifts.forkliftId,
|
||||||
|
// ],
|
||||||
|
// set: { amount: (excluded) => excluded.amount },
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
// 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]?.invoiceNumber} added`, data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
52
app/src/internal/forklifts/routes/invoices/getInvoices.ts
Normal file
52
app/src/internal/forklifts/routes/invoices/getInvoices.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { and, asc, eq, relations } 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 { forklifts } from "../../../../pkg/db/schema/forklifts.js";
|
||||||
|
import { leaseInvoices } from "../../../../pkg/db/schema/leaseInvoices.js";
|
||||||
|
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get("/", async (req: Request, res: Response) => {
|
||||||
|
const invoiceNumber = req.query.lease;
|
||||||
|
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (invoiceNumber !== undefined) {
|
||||||
|
conditions.push(eq(leaseInvoices.invoiceNumber, `${invoiceNumber}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
//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(leaseInvoices)
|
||||||
|
//.innerJoin(forkliftCompanies, eq(forkliftCompanies.id, leases.companyId))
|
||||||
|
.where(and(...conditions))
|
||||||
|
.orderBy(asc(leaseInvoices.invoiceNumber)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).json({ error: error });
|
||||||
|
}
|
||||||
|
res.status(200).json({ message: "Current Leases", data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
22
app/src/internal/forklifts/routes/invoices/invoiceRoutes.ts
Normal file
22
app/src/internal/forklifts/routes/invoices/invoiceRoutes.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
|
||||||
|
|
||||||
|
import addInvoice from "./addInvoice.js";
|
||||||
|
import getInvoices from "./getInvoices.js";
|
||||||
|
import updateInvoice from "./updateInvoices.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin", "manager"]),
|
||||||
|
getInvoices,
|
||||||
|
);
|
||||||
|
router.use("/", requireAuth("forklifts", ["systemAdmin", "admin"]), addInvoice);
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin"]),
|
||||||
|
updateInvoice,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
114
app/src/internal/forklifts/routes/invoices/updateInvoices.ts
Normal file
114
app/src/internal/forklifts/routes/invoices/updateInvoices.ts
Normal file
@@ -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 invoice",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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<string, any> = {};
|
||||||
|
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;
|
||||||
114
app/src/internal/forklifts/routes/leases/addLease.ts
Normal file
114
app/src/internal/forklifts/routes/leases/addLease.ts
Normal file
@@ -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;
|
||||||
57
app/src/internal/forklifts/routes/leases/getLeases.ts
Normal file
57
app/src/internal/forklifts/routes/leases/getLeases.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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 { forklifts } from "../../../../pkg/db/schema/forklifts.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)),
|
||||||
|
)) as any;
|
||||||
|
|
||||||
|
// add the forklifts that are in this lease
|
||||||
|
const forkliftData = await db.select().from(forklifts);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).json({ error: error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const leaseData = data.map((i: any) => ({
|
||||||
|
...i,
|
||||||
|
forklifts: forkliftData.filter((x) => x.leaseId === i.id),
|
||||||
|
}));
|
||||||
|
res.status(200).json({ message: "Current Leases", data: leaseData });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
18
app/src/internal/forklifts/routes/leases/leaseRoutes.ts
Normal file
18
app/src/internal/forklifts/routes/leases/leaseRoutes.ts
Normal file
@@ -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;
|
||||||
114
app/src/internal/forklifts/routes/leases/updateLease.ts
Normal file
114
app/src/internal/forklifts/routes/leases/updateLease.ts
Normal file
@@ -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<string, any> = {};
|
||||||
|
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;
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
import type { Express, Request, Response } from "express";
|
import type { Express, Request, Response } from "express";
|
||||||
import { requireAuth } from "../../../pkg/middleware/authMiddleware.js";
|
import { requireAuth } from "../../../pkg/middleware/authMiddleware.js";
|
||||||
import companies from "./companies/companiesRoutes.js";
|
import companies from "./companies/companiesRoutes.js";
|
||||||
|
import forklifts from "./forklifts/forkliftRoutes.js";
|
||||||
|
import invoices from "./invoices/invoiceRoutes.js";
|
||||||
|
import leases from "./leases/leaseRoutes.js";
|
||||||
export const setupForkliftRoutes = (app: Express, basePath: string) => {
|
export const setupForkliftRoutes = (app: Express, basePath: string) => {
|
||||||
|
app.use(
|
||||||
|
basePath + "/api/forklifts", // will pass bc system admin but this is just telling us we need this
|
||||||
|
forklifts,
|
||||||
|
);
|
||||||
app.use(
|
app.use(
|
||||||
basePath + "/api/forklifts/companies", // will pass bc system admin but this is just telling us we need this
|
basePath + "/api/forklifts/companies", // will pass bc system admin but this is just telling us we need this
|
||||||
companies,
|
companies,
|
||||||
);
|
);
|
||||||
|
app.use(
|
||||||
|
basePath + "/api/forklifts/leases", // will pass bc system admin but this is just telling us we need this
|
||||||
|
leases,
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
basePath + "/api/forklifts/invoices", // will pass bc system admin but this is just telling us we need this
|
||||||
|
invoices,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ const addProdLabel = async (
|
|||||||
if (prodLabelError) {
|
if (prodLabelError) {
|
||||||
log.error({ error: error }, "Error adding the label");
|
log.error({ error: error }, "Error adding the label");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
||||||
@@ -85,12 +87,12 @@ export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
|||||||
"POST",
|
"POST",
|
||||||
"/public/v1.0/Warehousing/GenerateAndPrintLabel",
|
"/public/v1.0/Warehousing/GenerateAndPrintLabel",
|
||||||
{
|
{
|
||||||
scannerId: preprint.scannerId,
|
scannerId: preprint.scannerId.toString(),
|
||||||
lotNr: preprint.lotNr,
|
lotNr: preprint.lotNr.toString(),
|
||||||
machineId: preprint.machineId, // 457=22, 458=23
|
machineId: preprint.machineId.toString(),
|
||||||
printerId: preprint.printerId, // 457=22, 458=23
|
printerId: preprint.printerId.toString(),
|
||||||
layoutId: preprint.layoutId,
|
layoutId: preprint.layoutId.toString(),
|
||||||
numberOfCopies: preprint.numberOfCopies,
|
numberOfCopies: preprint.numberOfCopies.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (labels?.data.Result === 1) {
|
if (labels?.data.Result === 1) {
|
||||||
@@ -116,7 +118,7 @@ export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
|||||||
}
|
}
|
||||||
labelsPrinted.push(parseInt(labels?.data.SSCC.slice(10, -1)));
|
labelsPrinted.push(parseInt(labels?.data.SSCC.slice(10, -1)));
|
||||||
// add the label to our label db for tracking purposes
|
// add the label to our label db for tracking purposes
|
||||||
addProdLabel(
|
await addProdLabel(
|
||||||
preprint,
|
preprint,
|
||||||
parseInt(labels?.data.SSCC.slice(10, -1)),
|
parseInt(labels?.data.SSCC.slice(10, -1)),
|
||||||
username || "lst",
|
username || "lst",
|
||||||
@@ -141,12 +143,12 @@ export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
|||||||
"POST",
|
"POST",
|
||||||
"/public/v1.0/Warehousing/GenerateAndPrintLabel",
|
"/public/v1.0/Warehousing/GenerateAndPrintLabel",
|
||||||
{
|
{
|
||||||
scannerId: preprint.scannerId,
|
scannerId: preprint.scannerId.toString(),
|
||||||
lotNr: preprint.lotNr,
|
lotNr: preprint.lotNr.toString(),
|
||||||
machineId: preprint.machineId, // 457=22, 458=23
|
machineId: preprint.machineId.toString(),
|
||||||
printerId: preprint.printerId, // 457=22, 458=23
|
printerId: preprint.printerId.toString(),
|
||||||
layoutId: preprint.layoutId,
|
layoutId: preprint.layoutId.toString(),
|
||||||
numberOfCopies: preprint.numberOfCopies,
|
numberOfCopies: preprint.numberOfCopies.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -173,7 +175,7 @@ export const preprintLabels = async (preprint: Preprint, username?: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
labelsPrinted.push(parseInt(labels.data.SSCC.slice(10, -1)));
|
labelsPrinted.push(parseInt(labels.data.SSCC.slice(10, -1)));
|
||||||
addProdLabel(
|
await addProdLabel(
|
||||||
preprint,
|
preprint,
|
||||||
parseInt(labels?.data.SSCC.slice(10, -1)),
|
parseInt(labels?.data.SSCC.slice(10, -1)),
|
||||||
username || "lst",
|
username || "lst",
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
import { date, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
import { date, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
import { createSelectSchema } from "drizzle-zod";
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import z from "zod";
|
||||||
import { forkliftCompanies } from "./forkliftLeaseCompanys.js";
|
import { forkliftCompanies } from "./forkliftLeaseCompanys.js";
|
||||||
|
|
||||||
export const leases = pgTable("leases", {
|
export const leases = pgTable("leases", {
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
leaseNumber: text("lease_number").notNull(),
|
leaseNumber: text("lease_number").unique().notNull(),
|
||||||
companyId: uuid("company_id").references(() => forkliftCompanies.id),
|
companyId: uuid("company_id").references(() => forkliftCompanies.id),
|
||||||
startDate: date("start_date"),
|
startDate: date("start_date"),
|
||||||
endDate: date("end_date"),
|
endDate: date("end_date"),
|
||||||
leaseLink: text("lease_link"),
|
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 selectLeasesDataSchema = createSelectSchema(leases);
|
||||||
|
|
||||||
|
export const insertLeasesCompanySchema = createInsertSchema(leases).extend({
|
||||||
|
leaseNumber: z.string().min(3),
|
||||||
|
// zipcode: z
|
||||||
|
// .string()
|
||||||
|
// .regex(/^\d{5}$/)
|
||||||
|
// .optional(),
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core";
|
import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { forklifts } from "./forklifts.js";
|
import { forklifts } from "./forklifts.js";
|
||||||
import { leaseInvoices } from "./leaseInvoices.js";
|
import { leaseInvoices } from "./leaseInvoices.js";
|
||||||
|
|
||||||
export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
|
export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
|
||||||
id: serial("id").primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
invoiceId: uuid("invoice_id")
|
invoiceId: uuid("invoice_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => leaseInvoices.id, { onDelete: "cascade" }),
|
.references(() => leaseInvoices.id, { onDelete: "cascade" }),
|
||||||
@@ -12,3 +13,7 @@ export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
|
|||||||
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
|
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
|
||||||
amount: numeric("amount"), // optional: amount of invoice allocated to this lift
|
amount: numeric("amount"), // optional: amount of invoice allocated to this lift
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const newForkliftInvoiceSchema = createInsertSchema(
|
||||||
|
leaseInvoiceForklifts,
|
||||||
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const status = pgEnum("forklift_status", [
|
|||||||
export const forklifts = pgTable("forklifts", {
|
export const forklifts = pgTable("forklifts", {
|
||||||
forklift_id: uuid("forklift_id").defaultRandom().primaryKey(),
|
forklift_id: uuid("forklift_id").defaultRandom().primaryKey(),
|
||||||
forkliftNumber: serial("forklift_number").notNull(),
|
forkliftNumber: serial("forklift_number").notNull(),
|
||||||
serialNumber: text("serial_number").notNull(),
|
serialNumber: text("serial_number").unique().notNull(),
|
||||||
model: text("model").notNull(),
|
model: text("model").notNull(),
|
||||||
plant: text("plant")
|
plant: text("plant")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -41,8 +41,8 @@ export const forklifts = pgTable("forklifts", {
|
|||||||
dataPlate: text("data_plate"),
|
dataPlate: text("data_plate"),
|
||||||
add_date: timestamp("add_date").defaultNow(),
|
add_date: timestamp("add_date").defaultNow(),
|
||||||
add_user: text("add_user").default("LST"),
|
add_user: text("add_user").default("LST"),
|
||||||
upd_date: timestamp("add_date").defaultNow(),
|
upd_date: timestamp("upd_date").defaultNow(),
|
||||||
upd_user: text("add_user").default("LST"),
|
upd_user: text("upd_user").default("LST"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const forkliftsSchema = createSelectSchema(forklifts);
|
export const forkliftsSchema = createSelectSchema(forklifts);
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
import { date, numeric, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
import {
|
||||||
import { forkliftCompanies } from "./forkliftLeaseCompanys.js";
|
date,
|
||||||
|
numeric,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { leases } from "./forkliftLeases.js";
|
import { leases } from "./forkliftLeases.js";
|
||||||
import { forklifts } from "./forklifts.js";
|
|
||||||
|
|
||||||
export const leaseInvoices = pgTable("lease_invoices", {
|
export const leaseInvoices = pgTable("lease_invoices", {
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
leaseId: uuid("lease_id")
|
leaseId: uuid("lease_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => leases.id, { onDelete: "cascade" }),
|
.references(() => leases.id, { onDelete: "cascade" }),
|
||||||
companyId: uuid("company_id").references(() => forkliftCompanies.id),
|
invoiceNumber: text("invoice_number").unique().notNull(),
|
||||||
invoiceNumber: text("invoice_number").notNull(),
|
|
||||||
invoiceDate: date("invoice_date").notNull(),
|
invoiceDate: date("invoice_date").notNull(),
|
||||||
forkliftId: uuid("forklift_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
|
|
||||||
totalAmount: numeric("total_amount"),
|
totalAmount: numeric("total_amount"),
|
||||||
|
add_date: timestamp("add_date"),
|
||||||
uploadedBy: text("uploaded_by"),
|
uploadedBy: text("uploaded_by"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const newInvoiceSchema = createInsertSchema(leaseInvoices);
|
||||||
|
|||||||
134
frontend/package-lock.json
generated
134
frontend/package-lock.json
generated
@@ -19,7 +19,7 @@
|
|||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
@@ -1720,6 +1720,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-compose-refs": {
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||||
@@ -1786,6 +1804,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-direction": {
|
"node_modules/@radix-ui/react-direction": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
||||||
@@ -1978,6 +2014,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popover": {
|
"node_modules/@radix-ui/react-popover": {
|
||||||
"version": "1.1.15",
|
"version": "1.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
|
||||||
@@ -2015,6 +2069,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-popper": {
|
"node_modules/@radix-ui/react-popper": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
|
||||||
@@ -2118,6 +2190,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-roving-focus": {
|
"node_modules/@radix-ui/react-roving-focus": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
|
||||||
@@ -2223,6 +2313,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-separator": {
|
"node_modules/@radix-ui/react-separator": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz",
|
||||||
@@ -2247,9 +2355,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
|
||||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-compose-refs": "1.1.2"
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
@@ -2357,6 +2465,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-switch": "^1.2.6",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function ForkliftSideBar() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Leases",
|
title: "Leases",
|
||||||
url: "/lst/app/admin/settings",
|
url: "/lst/app/forklifts/leases",
|
||||||
icon: ReceiptText,
|
icon: ReceiptText,
|
||||||
role: ["systemAdmin", "admin"],
|
role: ["systemAdmin", "admin"],
|
||||||
module: "forklifts",
|
module: "forklifts",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Link, useRouterState } from "@tanstack/react-router";
|
import { Link, useRouterState } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import NewCompanyForm from "@/routes/_app/_forklifts/-components/NewCompany";
|
import NewCompanyForm from "@/routes/_app/_forklifts/-components/NewCompany";
|
||||||
|
import NewLeaseForm from "@/routes/_app/_forklifts/-components/NewLease";
|
||||||
import { useAuth, useLogout } from "../../lib/authClient";
|
import { useAuth, useLogout } from "../../lib/authClient";
|
||||||
import { ModeToggle } from "../mode-toggle";
|
import { ModeToggle } from "../mode-toggle";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||||
@@ -21,6 +22,7 @@ export default function Nav() {
|
|||||||
const router = useRouterState();
|
const router = useRouterState();
|
||||||
const currentPath = router.location.href;
|
const currentPath = router.location.href;
|
||||||
const [openDialog, setOpenDialog] = useState(false);
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
const [openLeaseDialog, setOpenLeaseDialog] = useState(false);
|
||||||
return (
|
return (
|
||||||
<nav className="flex justify-end w-full shadow ">
|
<nav className="flex justify-end w-full shadow ">
|
||||||
<div className="m-2 flex flex-row gap-1">
|
<div className="m-2 flex flex-row gap-1">
|
||||||
@@ -41,7 +43,7 @@ export default function Nav() {
|
|||||||
{location.pathname.includes("forklifts") && (
|
{location.pathname.includes("forklifts") && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button>Forklifts</Button>
|
<Button>Forklifts</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
@@ -55,9 +57,17 @@ export default function Nav() {
|
|||||||
>
|
>
|
||||||
New Company
|
New Company
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={() => {
|
||||||
|
// just open the dialog when clicked
|
||||||
|
setOpenLeaseDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Lease
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{/* Dialog mounted outside the menu */}
|
{/* Company */}
|
||||||
{openDialog && (
|
{openDialog && (
|
||||||
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
|
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
@@ -65,6 +75,16 @@ export default function Nav() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
{openLeaseDialog && (
|
||||||
|
<Dialog
|
||||||
|
open={openLeaseDialog}
|
||||||
|
onOpenChange={setOpenLeaseDialog}
|
||||||
|
>
|
||||||
|
<DialogContent className="sm:max-w-fit">
|
||||||
|
<NewLeaseForm setOpenDialog={setOpenLeaseDialog} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,58 +1,60 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
destructive:
|
||||||
destructive:
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
outline:
|
||||||
outline:
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
secondary:
|
||||||
secondary:
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
ghost:
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
},
|
"icon-sm": "size-8",
|
||||||
},
|
"icon-lg": "size-10",
|
||||||
defaultVariants: {
|
},
|
||||||
variant: "default",
|
},
|
||||||
size: "default",
|
defaultVariants: {
|
||||||
},
|
variant: "default",
|
||||||
}
|
size: "default",
|
||||||
);
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean;
|
asChild?: boolean
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "button";
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="button"
|
data-slot="button"
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants }
|
||||||
|
|||||||
@@ -1,225 +1,214 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react"
|
||||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||||
import { buttonVariants, Button } from "./button";
|
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
classNames,
|
classNames,
|
||||||
showOutsideDays = true,
|
showOutsideDays = true,
|
||||||
captionLayout = "label",
|
captionLayout = "label",
|
||||||
buttonVariant = "ghost",
|
buttonVariant = "ghost",
|
||||||
formatters,
|
formatters,
|
||||||
components,
|
components,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayPicker> & {
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
|
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
||||||
}) {
|
}) {
|
||||||
const defaultClassNames = getDefaultClassNames();
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DayPicker
|
<DayPicker
|
||||||
showOutsideDays={showOutsideDays}
|
showOutsideDays={showOutsideDays}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
captionLayout={captionLayout}
|
captionLayout={captionLayout}
|
||||||
formatters={{
|
formatters={{
|
||||||
formatMonthDropdown: (date) =>
|
formatMonthDropdown: (date) =>
|
||||||
date.toLocaleString("default", { month: "short" }),
|
date.toLocaleString("default", { month: "short" }),
|
||||||
...formatters,
|
...formatters,
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
root: cn("w-fit", defaultClassNames.root),
|
root: cn("w-fit", defaultClassNames.root),
|
||||||
months: cn(
|
months: cn(
|
||||||
"flex gap-4 flex-col md:flex-row relative",
|
"flex gap-4 flex-col md:flex-row relative",
|
||||||
defaultClassNames.months
|
defaultClassNames.months
|
||||||
),
|
),
|
||||||
month: cn(
|
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
|
||||||
"flex flex-col w-full gap-4",
|
nav: cn(
|
||||||
defaultClassNames.month
|
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
|
||||||
),
|
defaultClassNames.nav
|
||||||
nav: cn(
|
),
|
||||||
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
|
button_previous: cn(
|
||||||
defaultClassNames.nav
|
buttonVariants({ variant: buttonVariant }),
|
||||||
),
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
||||||
button_previous: cn(
|
defaultClassNames.button_previous
|
||||||
buttonVariants({ variant: buttonVariant }),
|
),
|
||||||
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
button_next: cn(
|
||||||
defaultClassNames.button_previous
|
buttonVariants({ variant: buttonVariant }),
|
||||||
),
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
||||||
button_next: cn(
|
defaultClassNames.button_next
|
||||||
buttonVariants({ variant: buttonVariant }),
|
),
|
||||||
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
|
month_caption: cn(
|
||||||
defaultClassNames.button_next
|
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
|
||||||
),
|
defaultClassNames.month_caption
|
||||||
month_caption: cn(
|
),
|
||||||
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
|
dropdowns: cn(
|
||||||
defaultClassNames.month_caption
|
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
|
||||||
),
|
defaultClassNames.dropdowns
|
||||||
dropdowns: cn(
|
),
|
||||||
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
|
dropdown_root: cn(
|
||||||
defaultClassNames.dropdowns
|
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
|
||||||
),
|
defaultClassNames.dropdown_root
|
||||||
dropdown_root: cn(
|
),
|
||||||
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
|
dropdown: cn(
|
||||||
defaultClassNames.dropdown_root
|
"absolute bg-popover inset-0 opacity-0",
|
||||||
),
|
defaultClassNames.dropdown
|
||||||
dropdown: cn(
|
),
|
||||||
"absolute bg-popover inset-0 opacity-0",
|
caption_label: cn(
|
||||||
defaultClassNames.dropdown
|
"select-none font-medium",
|
||||||
),
|
captionLayout === "label"
|
||||||
caption_label: cn(
|
? "text-sm"
|
||||||
"select-none font-medium",
|
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
|
||||||
captionLayout === "label"
|
defaultClassNames.caption_label
|
||||||
? "text-sm"
|
),
|
||||||
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
|
table: "w-full border-collapse",
|
||||||
defaultClassNames.caption_label
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
),
|
weekday: cn(
|
||||||
table: "w-full border-collapse",
|
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
defaultClassNames.weekday
|
||||||
weekday: cn(
|
),
|
||||||
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
|
week: cn("flex w-full mt-2", defaultClassNames.week),
|
||||||
defaultClassNames.weekday
|
week_number_header: cn(
|
||||||
),
|
"select-none w-(--cell-size)",
|
||||||
week: cn("flex w-full mt-2", defaultClassNames.week),
|
defaultClassNames.week_number_header
|
||||||
week_number_header: cn(
|
),
|
||||||
"select-none w-(--cell-size)",
|
week_number: cn(
|
||||||
defaultClassNames.week_number_header
|
"text-[0.8rem] select-none text-muted-foreground",
|
||||||
),
|
defaultClassNames.week_number
|
||||||
week_number: cn(
|
),
|
||||||
"text-[0.8rem] select-none text-muted-foreground",
|
day: cn(
|
||||||
defaultClassNames.week_number
|
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
||||||
),
|
props.showWeekNumber
|
||||||
day: cn(
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
|
||||||
"relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
|
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
|
||||||
defaultClassNames.day
|
defaultClassNames.day
|
||||||
),
|
),
|
||||||
range_start: cn(
|
range_start: cn(
|
||||||
"rounded-l-md bg-accent",
|
"rounded-l-md bg-accent",
|
||||||
defaultClassNames.range_start
|
defaultClassNames.range_start
|
||||||
),
|
),
|
||||||
range_middle: cn(
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||||
"rounded-none",
|
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
|
||||||
defaultClassNames.range_middle
|
today: cn(
|
||||||
),
|
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
||||||
range_end: cn(
|
defaultClassNames.today
|
||||||
"rounded-r-md bg-accent",
|
),
|
||||||
defaultClassNames.range_end
|
outside: cn(
|
||||||
),
|
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||||
today: cn(
|
defaultClassNames.outside
|
||||||
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
),
|
||||||
defaultClassNames.today
|
disabled: cn(
|
||||||
),
|
"text-muted-foreground opacity-50",
|
||||||
outside: cn(
|
defaultClassNames.disabled
|
||||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
),
|
||||||
defaultClassNames.outside
|
hidden: cn("invisible", defaultClassNames.hidden),
|
||||||
),
|
...classNames,
|
||||||
disabled: cn(
|
}}
|
||||||
"text-muted-foreground opacity-50",
|
components={{
|
||||||
defaultClassNames.disabled
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
),
|
return (
|
||||||
hidden: cn("invisible", defaultClassNames.hidden),
|
<div
|
||||||
...classNames,
|
data-slot="calendar"
|
||||||
}}
|
ref={rootRef}
|
||||||
components={{
|
className={cn(className)}
|
||||||
Root: ({ className, rootRef, ...props }) => {
|
{...props}
|
||||||
return (
|
/>
|
||||||
<div
|
)
|
||||||
data-slot="calendar"
|
},
|
||||||
ref={rootRef}
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
className={cn(className)}
|
if (orientation === "left") {
|
||||||
{...props}
|
return (
|
||||||
/>
|
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||||
);
|
)
|
||||||
},
|
}
|
||||||
Chevron: ({ className, orientation, ...props }) => {
|
|
||||||
if (orientation === "left") {
|
|
||||||
return (
|
|
||||||
<ChevronLeftIcon
|
|
||||||
className={cn("size-4", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orientation === "right") {
|
if (orientation === "right") {
|
||||||
return (
|
return (
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className={cn("size-4", className)}
|
className={cn("size-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||||
className={cn("size-4", className)}
|
)
|
||||||
{...props}
|
},
|
||||||
/>
|
DayButton: CalendarDayButton,
|
||||||
);
|
WeekNumber: ({ children, ...props }) => {
|
||||||
},
|
return (
|
||||||
DayButton: CalendarDayButton,
|
<td {...props}>
|
||||||
WeekNumber: ({ children, ...props }) => {
|
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
||||||
return (
|
{children}
|
||||||
<td {...props}>
|
</div>
|
||||||
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
</td>
|
||||||
{children}
|
)
|
||||||
</div>
|
},
|
||||||
</td>
|
...components,
|
||||||
);
|
}}
|
||||||
},
|
{...props}
|
||||||
...components,
|
/>
|
||||||
}}
|
)
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function CalendarDayButton({
|
function CalendarDayButton({
|
||||||
className,
|
className,
|
||||||
day,
|
day,
|
||||||
modifiers,
|
modifiers,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DayButton>) {
|
}: React.ComponentProps<typeof DayButton>) {
|
||||||
const defaultClassNames = getDefaultClassNames();
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
const ref = React.useRef<HTMLButtonElement>(null);
|
const ref = React.useRef<HTMLButtonElement>(null)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (modifiers.focused) ref.current?.focus();
|
if (modifiers.focused) ref.current?.focus()
|
||||||
}, [modifiers.focused]);
|
}, [modifiers.focused])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
data-day={day.date.toLocaleDateString()}
|
data-day={day.date.toLocaleDateString()}
|
||||||
data-selected-single={
|
data-selected-single={
|
||||||
modifiers.selected &&
|
modifiers.selected &&
|
||||||
!modifiers.range_start &&
|
!modifiers.range_start &&
|
||||||
!modifiers.range_end &&
|
!modifiers.range_end &&
|
||||||
!modifiers.range_middle
|
!modifiers.range_middle
|
||||||
}
|
}
|
||||||
data-range-start={modifiers.range_start}
|
data-range-start={modifiers.range_start}
|
||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
|
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
|
||||||
defaultClassNames.day,
|
defaultClassNames.day,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Calendar, CalendarDayButton };
|
export { Calendar, CalendarDayButton }
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Calendar as CalendarIcon } from "lucide-react";
|
import { Calendar as CalendarIcon } from "lucide-react";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { Calendar } from "./calendar";
|
import { Calendar } from "./calendar";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||||
|
|
||||||
export function DatePicker({
|
export function DatePicker({
|
||||||
date,
|
date,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
date?: Date;
|
date?: Date;
|
||||||
onChange?: (d: Date | undefined) => void;
|
onChange?: (d: Date | undefined) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
data-empty={!date}
|
data-empty={!date}
|
||||||
className="data-[empty=true]:text-muted-foreground w-[200px] justify-start text-left font-normal"
|
className="data-[empty=true]:text-muted-foreground w-[200px] justify-start text-left font-normal"
|
||||||
>
|
>
|
||||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0">
|
<PopoverContent className="w-auto p-0">
|
||||||
<Calendar mode="single" selected={date} onSelect={onChange} />
|
<Calendar mode="single" selected={date} onSelect={onChange} />
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,263 +1,255 @@
|
|||||||
import * as React from "react";
|
import * as React from "react"
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function DropdownMenu({
|
function DropdownMenu({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuPortal({
|
function DropdownMenuPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal
|
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
data-slot="dropdown-menu-portal"
|
)
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuTrigger({
|
function DropdownMenuTrigger({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<DropdownMenuPrimitive.Trigger
|
||||||
data-slot="dropdown-menu-trigger"
|
data-slot="dropdown-menu-trigger"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuContent({
|
function DropdownMenuContent({
|
||||||
className,
|
className,
|
||||||
sideOffset = 4,
|
sideOffset = 4,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal>
|
<DropdownMenuPrimitive.Portal>
|
||||||
<DropdownMenuPrimitive.Content
|
<DropdownMenuPrimitive.Content
|
||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuGroup({
|
function DropdownMenuGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Group
|
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
data-slot="dropdown-menu-group"
|
)
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuItem({
|
function DropdownMenuItem({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||||
inset?: boolean;
|
inset?: boolean
|
||||||
variant?: "default" | "destructive";
|
variant?: "default" | "destructive"
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
data-slot="dropdown-menu-item"
|
data-slot="dropdown-menu-item"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuCheckboxItem({
|
function DropdownMenuCheckboxItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
checked,
|
checked,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
<CheckIcon className="size-4" />
|
<CheckIcon className="size-4" />
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({
|
function DropdownMenuRadioGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioGroup
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
data-slot="dropdown-menu-radio-group"
|
data-slot="dropdown-menu-radio-group"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioItem({
|
function DropdownMenuRadioItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
<CircleIcon className="size-2 fill-current" />
|
<CircleIcon className="size-2 fill-current" />
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuLabel({
|
function DropdownMenuLabel({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||||
inset?: boolean;
|
inset?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Label
|
<DropdownMenuPrimitive.Label
|
||||||
data-slot="dropdown-menu-label"
|
data-slot="dropdown-menu-label"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSeparator({
|
function DropdownMenuSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Separator
|
<DropdownMenuPrimitive.Separator
|
||||||
data-slot="dropdown-menu-separator"
|
data-slot="dropdown-menu-separator"
|
||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuShortcut({
|
function DropdownMenuShortcut({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span">) {
|
}: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="dropdown-menu-shortcut"
|
data-slot="dropdown-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSub({
|
function DropdownMenuSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
return (
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||||
<DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSubTrigger({
|
function DropdownMenuSubTrigger({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean;
|
inset?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
data-slot="dropdown-menu-sub-trigger"
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronRightIcon className="ml-auto size-4" />
|
<ChevronRightIcon className="ml-auto size-4" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSubContent({
|
function DropdownMenuSubContent({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
};
|
}
|
||||||
|
|||||||
53
frontend/src/lib/formStuff/components/CalenderSelect.tsx
Normal file
53
frontend/src/lib/formStuff/components/CalenderSelect.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Label } from "../../../components/ui/label";
|
||||||
|
import { useFieldContext } from "..";
|
||||||
|
import { FieldErrors } from "./FieldErrors";
|
||||||
|
|
||||||
|
type DateFieldProps = {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export const DateField = ({ label }: DateFieldProps) => {
|
||||||
|
const field = useFieldContext<any>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const date = field.state.value;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-3">
|
||||||
|
<Label htmlFor={field.name}>{label}</Label>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
id="date"
|
||||||
|
className="w-48 justify-between font-normal"
|
||||||
|
>
|
||||||
|
{date ? date.toLocaleDateString() : "Select date"}
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
captionLayout="dropdown"
|
||||||
|
startMonth={new Date(new Date().getFullYear() - 10, 0)}
|
||||||
|
endMonth={new Date(new Date().getFullYear() + 20, 0)}
|
||||||
|
onSelect={(selected) => {
|
||||||
|
field.handleChange(selected ?? undefined);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FieldErrors meta={field.state.meta} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
|
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
|
||||||
import { SubmitButton } from "./components/SubmitButton";
|
import { DateField } from "./components/CalenderSelect";
|
||||||
import { InputField } from "./components/InputField";
|
|
||||||
import { SelectField } from "./components/SelectField";
|
|
||||||
import { CheckboxField } from "./components/CheckBox";
|
import { CheckboxField } from "./components/CheckBox";
|
||||||
|
import { InputField } from "./components/InputField";
|
||||||
import { InputPasswordField } from "./components/InputPasswordField";
|
import { InputPasswordField } from "./components/InputPasswordField";
|
||||||
|
import { SelectField } from "./components/SelectField";
|
||||||
|
import { SubmitButton } from "./components/SubmitButton";
|
||||||
|
|
||||||
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
||||||
createFormHookContexts();
|
createFormHookContexts();
|
||||||
|
|
||||||
export const { useAppForm } = createFormHook({
|
export const { useAppForm } = createFormHook({
|
||||||
fieldComponents: {
|
fieldComponents: {
|
||||||
InputField,
|
InputField,
|
||||||
InputPasswordField,
|
InputPasswordField,
|
||||||
SelectField,
|
SelectField,
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
},
|
DateField,
|
||||||
formComponents: { SubmitButton },
|
},
|
||||||
fieldContext,
|
formComponents: { SubmitButton },
|
||||||
formContext,
|
fieldContext,
|
||||||
|
formContext,
|
||||||
});
|
});
|
||||||
|
|||||||
17
frontend/src/lib/querys/forklifts/getLeases.ts
Normal file
17
frontend/src/lib/querys/forklifts/getLeases.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { queryOptions } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export function getLeases() {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["getLeases"],
|
||||||
|
queryFn: () => fetch(),
|
||||||
|
staleTime: 5000,
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const { data } = await axios.get("/lst/api/forklifts/leases");
|
||||||
|
|
||||||
|
return data.data;
|
||||||
|
};
|
||||||
112
frontend/src/lib/tableStuff/TableNoExpand.tsx
Normal file
112
frontend/src/lib/tableStuff/TableNoExpand.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
type SortingState,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
|
export default function TableNoExpand({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
}: {
|
||||||
|
data: any;
|
||||||
|
columns: any;
|
||||||
|
}) {
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
|
||||||
|
//getRowCanExpand: () => true,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="w-fit">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<React.Fragment key={row.id}>
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
{/* {row.getIsExpanded() && (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={row.getVisibleCells().length}>
|
||||||
|
{renderSubComponent({ row })}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)} */}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import { Route as AppauthUserIndexRouteImport } from './routes/_app/(auth)/user/
|
|||||||
import { Route as MobileMobileLayoutMRelocateRouteImport } from './routes/_mobile/_mobileLayout/m/relocate'
|
import { Route as MobileMobileLayoutMRelocateRouteImport } from './routes/_mobile/_mobileLayout/m/relocate'
|
||||||
import { Route as MobileMobileLayoutMDeliveryRouteImport } from './routes/_mobile/_mobileLayout/m/delivery'
|
import { Route as MobileMobileLayoutMDeliveryRouteImport } from './routes/_mobile/_mobileLayout/m/delivery'
|
||||||
import { Route as MobileMobileLayoutMCyclecountsRouteImport } from './routes/_mobile/_mobileLayout/m/cyclecounts'
|
import { Route as MobileMobileLayoutMCyclecountsRouteImport } from './routes/_mobile/_mobileLayout/m/cyclecounts'
|
||||||
|
import { Route as AppForkliftsForkliftsLeasesRouteImport } from './routes/_app/_forklifts/forklifts/leases'
|
||||||
import { Route as AppForkliftsForkliftsCompaniesRouteImport } from './routes/_app/_forklifts/forklifts/companies'
|
import { Route as AppForkliftsForkliftsCompaniesRouteImport } from './routes/_app/_forklifts/forklifts/companies'
|
||||||
import { Route as AppAdminLayoutAdminServersRouteImport } from './routes/_app/_adminLayout/admin/servers'
|
import { Route as AppAdminLayoutAdminServersRouteImport } from './routes/_app/_adminLayout/admin/servers'
|
||||||
import { Route as ApplogisticsLogisticsDeliveryScheduleRouteImport } from './routes/_app/(logistics)/logistics/deliverySchedule'
|
import { Route as ApplogisticsLogisticsDeliveryScheduleRouteImport } from './routes/_app/(logistics)/logistics/deliverySchedule'
|
||||||
@@ -145,6 +146,12 @@ const MobileMobileLayoutMCyclecountsRoute =
|
|||||||
path: '/m/cyclecounts',
|
path: '/m/cyclecounts',
|
||||||
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AppForkliftsForkliftsLeasesRoute =
|
||||||
|
AppForkliftsForkliftsLeasesRouteImport.update({
|
||||||
|
id: '/forklifts/leases',
|
||||||
|
path: '/forklifts/leases',
|
||||||
|
getParentRoute: () => AppForkliftsRouteRoute,
|
||||||
|
} as any)
|
||||||
const AppForkliftsForkliftsCompaniesRoute =
|
const AppForkliftsForkliftsCompaniesRoute =
|
||||||
AppForkliftsForkliftsCompaniesRouteImport.update({
|
AppForkliftsForkliftsCompaniesRouteImport.update({
|
||||||
id: '/forklifts/companies',
|
id: '/forklifts/companies',
|
||||||
@@ -280,6 +287,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||||
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
|
'/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||||
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
||||||
@@ -314,6 +322,7 @@ export interface FileRoutesByTo {
|
|||||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||||
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
|
'/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||||
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
||||||
@@ -356,6 +365,7 @@ export interface FileRoutesById {
|
|||||||
'/_app/(logistics)/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
'/_app/(logistics)/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||||
'/_app/_adminLayout/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
'/_app/_adminLayout/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||||
'/_app/_forklifts/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
'/_app/_forklifts/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
|
'/_app/_forklifts/forklifts/leases': typeof AppForkliftsForkliftsLeasesRoute
|
||||||
'/_mobile/_mobileLayout/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
'/_mobile/_mobileLayout/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||||
'/_mobile/_mobileLayout/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
'/_mobile/_mobileLayout/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||||
'/_mobile/_mobileLayout/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
'/_mobile/_mobileLayout/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
||||||
@@ -393,6 +403,7 @@ export interface FileRouteTypes {
|
|||||||
| '/logistics/deliverySchedule'
|
| '/logistics/deliverySchedule'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
| '/forklifts/companies'
|
| '/forklifts/companies'
|
||||||
|
| '/forklifts/leases'
|
||||||
| '/m/cyclecounts'
|
| '/m/cyclecounts'
|
||||||
| '/m/delivery'
|
| '/m/delivery'
|
||||||
| '/m/relocate'
|
| '/m/relocate'
|
||||||
@@ -427,6 +438,7 @@ export interface FileRouteTypes {
|
|||||||
| '/logistics/deliverySchedule'
|
| '/logistics/deliverySchedule'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
| '/forklifts/companies'
|
| '/forklifts/companies'
|
||||||
|
| '/forklifts/leases'
|
||||||
| '/m/cyclecounts'
|
| '/m/cyclecounts'
|
||||||
| '/m/delivery'
|
| '/m/delivery'
|
||||||
| '/m/relocate'
|
| '/m/relocate'
|
||||||
@@ -468,6 +480,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_app/(logistics)/logistics/deliverySchedule'
|
| '/_app/(logistics)/logistics/deliverySchedule'
|
||||||
| '/_app/_adminLayout/admin/servers'
|
| '/_app/_adminLayout/admin/servers'
|
||||||
| '/_app/_forklifts/forklifts/companies'
|
| '/_app/_forklifts/forklifts/companies'
|
||||||
|
| '/_app/_forklifts/forklifts/leases'
|
||||||
| '/_mobile/_mobileLayout/m/cyclecounts'
|
| '/_mobile/_mobileLayout/m/cyclecounts'
|
||||||
| '/_mobile/_mobileLayout/m/delivery'
|
| '/_mobile/_mobileLayout/m/delivery'
|
||||||
| '/_mobile/_mobileLayout/m/relocate'
|
| '/_mobile/_mobileLayout/m/relocate'
|
||||||
@@ -625,6 +638,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof MobileMobileLayoutMCyclecountsRouteImport
|
preLoaderRoute: typeof MobileMobileLayoutMCyclecountsRouteImport
|
||||||
parentRoute: typeof MobileMobileLayoutRouteRoute
|
parentRoute: typeof MobileMobileLayoutRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_app/_forklifts/forklifts/leases': {
|
||||||
|
id: '/_app/_forklifts/forklifts/leases'
|
||||||
|
path: '/forklifts/leases'
|
||||||
|
fullPath: '/forklifts/leases'
|
||||||
|
preLoaderRoute: typeof AppForkliftsForkliftsLeasesRouteImport
|
||||||
|
parentRoute: typeof AppForkliftsRouteRoute
|
||||||
|
}
|
||||||
'/_app/_forklifts/forklifts/companies': {
|
'/_app/_forklifts/forklifts/companies': {
|
||||||
id: '/_app/_forklifts/forklifts/companies'
|
id: '/_app/_forklifts/forklifts/companies'
|
||||||
path: '/forklifts/companies'
|
path: '/forklifts/companies'
|
||||||
@@ -840,11 +860,13 @@ const AppAdminLayoutRouteRouteWithChildren =
|
|||||||
|
|
||||||
interface AppForkliftsRouteRouteChildren {
|
interface AppForkliftsRouteRouteChildren {
|
||||||
AppForkliftsForkliftsCompaniesRoute: typeof AppForkliftsForkliftsCompaniesRoute
|
AppForkliftsForkliftsCompaniesRoute: typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
|
AppForkliftsForkliftsLeasesRoute: typeof AppForkliftsForkliftsLeasesRoute
|
||||||
AppForkliftsForkliftsIndexRoute: typeof AppForkliftsForkliftsIndexRoute
|
AppForkliftsForkliftsIndexRoute: typeof AppForkliftsForkliftsIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppForkliftsRouteRouteChildren: AppForkliftsRouteRouteChildren = {
|
const AppForkliftsRouteRouteChildren: AppForkliftsRouteRouteChildren = {
|
||||||
AppForkliftsForkliftsCompaniesRoute: AppForkliftsForkliftsCompaniesRoute,
|
AppForkliftsForkliftsCompaniesRoute: AppForkliftsForkliftsCompaniesRoute,
|
||||||
|
AppForkliftsForkliftsLeasesRoute: AppForkliftsForkliftsLeasesRoute,
|
||||||
AppForkliftsForkliftsIndexRoute: AppForkliftsForkliftsIndexRoute,
|
AppForkliftsForkliftsIndexRoute: AppForkliftsForkliftsIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
128
frontend/src/routes/_app/_forklifts/-components/NewLease.tsx
Normal file
128
frontend/src/routes/_app/_forklifts/-components/NewLease.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DialogClose,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { getCompanies } from "@/lib/querys/forklifts/getCompanies";
|
||||||
|
import { getLeases } from "@/lib/querys/forklifts/getLeases";
|
||||||
|
import { useAppForm } from "../../../../lib/formStuff";
|
||||||
|
|
||||||
|
type CompanyData = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
export default function NewLeaseForm({
|
||||||
|
setOpenDialog,
|
||||||
|
}: {
|
||||||
|
setOpenDialog: any;
|
||||||
|
}) {
|
||||||
|
//const search = useSearch({ from: "/_app/(auth)/login" });
|
||||||
|
const { data, isLoading } = useQuery(getCompanies());
|
||||||
|
const { refetch } = useQuery(getLeases());
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
companyId: "",
|
||||||
|
leaseNumber: "",
|
||||||
|
startDate: "",
|
||||||
|
endDate: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
const data = {
|
||||||
|
leaseNumber: value.leaseNumber.trimStart().trimEnd(),
|
||||||
|
startDate: format(value.startDate, "MM/dd/yyyy"),
|
||||||
|
endDate: format(value.endDate, "MM/dd/yyyy"),
|
||||||
|
companyId: value.companyId,
|
||||||
|
};
|
||||||
|
console.log(data);
|
||||||
|
try {
|
||||||
|
await axios.post("/lst/api/forklifts/leases", data);
|
||||||
|
form.reset();
|
||||||
|
setOpenDialog(false);
|
||||||
|
refetch();
|
||||||
|
toast.success(`${value.leaseNumber} was just created `);
|
||||||
|
} catch (error) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (!error.response.data.success) {
|
||||||
|
// @ts-ignore
|
||||||
|
toast.error(error?.response?.data.message);
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
toast.error(error?.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) return <div>Loading Companies</div>;
|
||||||
|
|
||||||
|
// remap the companies to fit out select field
|
||||||
|
const companyMap = data.map((i: CompanyData) => {
|
||||||
|
return { value: i.id, label: i.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
//const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Lease</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Select the company this lease will be for, lease number, start and end
|
||||||
|
date
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form.AppField
|
||||||
|
name="companyId"
|
||||||
|
children={(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Select Company"
|
||||||
|
placeholder="Companies"
|
||||||
|
options={companyMap}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<form.AppField
|
||||||
|
name="leaseNumber"
|
||||||
|
children={(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Lease Number"
|
||||||
|
inputType="string"
|
||||||
|
required={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||||
|
<form.AppField
|
||||||
|
name="startDate"
|
||||||
|
children={(field) => <field.DateField label="Start Date" />}
|
||||||
|
/>
|
||||||
|
<form.AppField
|
||||||
|
name="endDate"
|
||||||
|
children={(field) => <field.DateField label="End Date" />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="outline">Cancel</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,9 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import {
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
createColumnHelper,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
type SortingState,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Activity, ArrowDown, ArrowUp } from "lucide-react";
|
import { Activity, ArrowDown, ArrowUp } from "lucide-react";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -22,15 +14,9 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { getCompanies } from "@/lib/querys/forklifts/getCompanies";
|
import { getCompanies } from "@/lib/querys/forklifts/getCompanies";
|
||||||
|
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type Company = {
|
type Company = {
|
||||||
@@ -64,7 +50,6 @@ function RouteComponent() {
|
|||||||
isLoading,
|
isLoading,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery(getCompanies());
|
} = useQuery(getCompanies());
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
|
||||||
const columnHelper = createColumnHelper<Company>();
|
const columnHelper = createColumnHelper<Company>();
|
||||||
const submitting = useRef(false);
|
const submitting = useRef(false);
|
||||||
|
|
||||||
@@ -192,91 +177,8 @@ function RouteComponent() {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: companyData,
|
|
||||||
columns,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
|
|
||||||
//getRowCanExpand: () => true,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className="m-auto">Loading user data</div>;
|
return <div className="m-auto">Loading user data</div>;
|
||||||
}
|
}
|
||||||
return (
|
return <TableNoExpand data={companyData} columns={columns} />;
|
||||||
<div className="p-4">
|
|
||||||
<div className="w-fit">
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext(),
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{table.getRowModel().rows.map((row) => (
|
|
||||||
<React.Fragment key={row.id}>
|
|
||||||
<TableRow
|
|
||||||
key={row.id}
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(
|
|
||||||
cell.column.columnDef.cell,
|
|
||||||
cell.getContext(),
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
{/* {row.getIsExpanded() && (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={row.getVisibleCells().length}>
|
|
||||||
{renderSubComponent({ row })}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)} */}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
124
frontend/src/routes/_app/_forklifts/forklifts/leases.tsx
Normal file
124
frontend/src/routes/_app/_forklifts/forklifts/leases.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { ArrowDown, ArrowUp } from "lucide-react";
|
||||||
|
//import { useRef } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { getLeases } from "@/lib/querys/forklifts/getLeases";
|
||||||
|
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||||
|
|
||||||
|
type Leases = {
|
||||||
|
id: string;
|
||||||
|
leaseNumber: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
leaseLink: string | null;
|
||||||
|
companyName: string;
|
||||||
|
add_user: string;
|
||||||
|
add_date: Date;
|
||||||
|
upd_user: string;
|
||||||
|
upd_date: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_app/_forklifts/forklifts/leases")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const { data: leaseData = [], isLoading } = useQuery(getLeases());
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<Leases>();
|
||||||
|
//const submitting = useRef(false);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor("companyName", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">Company</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("leaseNumber", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">Lease Number</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <span>{getValue()}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("startDate", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">Start Date</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
const raw = getValue() as string | Date;
|
||||||
|
const date = typeof raw === "string" ? new Date(raw) : (raw as Date);
|
||||||
|
if (isNaN(date.getTime())) return "Invalid date";
|
||||||
|
return <span>{format(date, "MM/dd/yyyy")}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("endDate", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">End Date</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
const raw = getValue() as string | Date;
|
||||||
|
const date = typeof raw === "string" ? new Date(raw) : (raw as Date);
|
||||||
|
if (isNaN(date.getTime())) return "Invalid date";
|
||||||
|
return <span>{format(date, "MM/dd/yyyy")}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div className="m-auto">Loading user data</div>;
|
||||||
|
}
|
||||||
|
return <TableNoExpand data={leaseData} columns={columns} />;
|
||||||
|
}
|
||||||
4
migrations/0037_cold_blur.sql
Normal file
4
migrations/0037_cold_blur.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE "leases" ADD COLUMN "add_date" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "leases" ADD COLUMN "add_user" text DEFAULT 'LST';--> statement-breakpoint
|
||||||
|
ALTER TABLE "leases" ADD COLUMN "upd_date" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "leases" ADD COLUMN "upd_user" text DEFAULT 'LST';
|
||||||
1
migrations/0038_secret_luminals.sql
Normal file
1
migrations/0038_secret_luminals.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "leases" ADD CONSTRAINT "leases_lease_number_unique" UNIQUE("lease_number");
|
||||||
2
migrations/0039_strange_iron_man.sql
Normal file
2
migrations/0039_strange_iron_man.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "forklifts" ADD COLUMN "upd_date" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD COLUMN "upd_user" text DEFAULT 'LST';
|
||||||
1
migrations/0040_keen_abomination.sql
Normal file
1
migrations/0040_keen_abomination.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_serial_number_unique" UNIQUE("serial_number");
|
||||||
6
migrations/0041_aspiring_chamber.sql
Normal file
6
migrations/0041_aspiring_chamber.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE "lease_invoices" DROP CONSTRAINT "lease_invoices_company_id_forklift_companies_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" DROP CONSTRAINT "lease_invoices_forklift_id_forklifts_forklift_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" DROP COLUMN "company_id";--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" DROP COLUMN "forklift_id";
|
||||||
1
migrations/0042_true_thor_girl.sql
Normal file
1
migrations/0042_true_thor_girl.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "lease_invoices" ADD COLUMN "add_date" timestamp;
|
||||||
1
migrations/0043_thick_sebastian_shaw.sql
Normal file
1
migrations/0043_thick_sebastian_shaw.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "lease_invoices" ADD CONSTRAINT "lease_invoices_invoice_number_unique" UNIQUE("invoice_number");
|
||||||
2
migrations/0044_melted_mole_man.sql
Normal file
2
migrations/0044_melted_mole_man.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "lease_invoice_forklifts" ALTER COLUMN "id" SET DATA TYPE uuid;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoice_forklifts" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||||
2208
migrations/meta/0037_snapshot.json
Normal file
2208
migrations/meta/0037_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2216
migrations/meta/0038_snapshot.json
Normal file
2216
migrations/meta/0038_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2230
migrations/meta/0039_snapshot.json
Normal file
2230
migrations/meta/0039_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2238
migrations/meta/0040_snapshot.json
Normal file
2238
migrations/meta/0040_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2200
migrations/meta/0041_snapshot.json
Normal file
2200
migrations/meta/0041_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2206
migrations/meta/0042_snapshot.json
Normal file
2206
migrations/meta/0042_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2214
migrations/meta/0043_snapshot.json
Normal file
2214
migrations/meta/0043_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2215
migrations/meta/0044_snapshot.json
Normal file
2215
migrations/meta/0044_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -260,6 +260,62 @@
|
|||||||
"when": 1762112909957,
|
"when": 1762112909957,
|
||||||
"tag": "0036_sticky_brood",
|
"tag": "0036_sticky_brood",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 37,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762298425546,
|
||||||
|
"tag": "0037_cold_blur",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 38,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762298736944,
|
||||||
|
"tag": "0038_secret_luminals",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 39,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762310353351,
|
||||||
|
"tag": "0039_strange_iron_man",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 40,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762310431830,
|
||||||
|
"tag": "0040_keen_abomination",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 41,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762311550798,
|
||||||
|
"tag": "0041_aspiring_chamber",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 42,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762312885208,
|
||||||
|
"tag": "0042_true_thor_girl",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 43,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762313144802,
|
||||||
|
"tag": "0043_thick_sebastian_shaw",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 44,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762314701057,
|
||||||
|
"tag": "0044_melted_mole_man",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user