feat(forklifts): added backend forklift stuff and frontend companies
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
meta {
|
meta {
|
||||||
name: Controller
|
name: Controller
|
||||||
seq: 1
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
meta {
|
meta {
|
||||||
name: LstV2
|
name: LstV2
|
||||||
seq: 3
|
seq: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ headers {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"name": "Dayton",
|
"name": "St Peters",
|
||||||
"serverDNS": "USDAY1VS006",
|
"serverDNS": "USSTP1VMS006",
|
||||||
"plantToken": "usday1",
|
"plantToken": "usstp1",
|
||||||
"ipAddress": "10.44.0.26",
|
"ipAddress": "10.37.0.26",
|
||||||
"greatPlainsPlantCode": 55,
|
"greatPlainsPlantCode": 45,
|
||||||
"lstServerPort": 4000,
|
"lstServerPort": 4000,
|
||||||
"serverLoc": "E$\\LST"
|
"serverLoc": "E:\\LST"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
meta {
|
meta {
|
||||||
name: app
|
name: app
|
||||||
seq: 2
|
seq: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: Get Companies
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{url}}/lst/api/forklifts/companies
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name":"Delage DLL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
meta {
|
||||||
|
name: Update Company
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
patch {
|
||||||
|
url: {{url}}/lst/api/forklifts/companies/:id
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:path {
|
||||||
|
id: fbfba3df-8c0f-4994-adae-c03808cbccdc
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name":"Delage DLL",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
meta {
|
||||||
|
name: add company
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{url}}/lst/api/forklifts/companies
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"name":"Delage DLL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: companies
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
8
LogisticsSupportTool_API_DOCS/app/forklifts/folder.bru
Normal file
8
LogisticsSupportTool_API_DOCS/app/forklifts/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: forklifts
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
105
app/src/internal/forklifts/routes/companies/addCompany.ts
Normal file
105
app/src/internal/forklifts/routes/companies/addCompany.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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 {
|
||||||
|
forkliftCompanies,
|
||||||
|
insertForkliftCompanySchema,
|
||||||
|
} from "../../../../pkg/db/schema/forkliftLeaseCompanys.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 company" });
|
||||||
|
const parsed = insertForkliftCompanySchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
return res.status(400).json({ errors: parsed.error.flatten() });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.insert(forkliftCompanies)
|
||||||
|
.values({
|
||||||
|
...parsed.data,
|
||||||
|
add_user: req.user?.username,
|
||||||
|
add_date: sql`NOW()`,
|
||||||
|
upd_user: req.user?.username,
|
||||||
|
upd_date: sql`NOW()`,
|
||||||
|
})
|
||||||
|
//.onConflictDoNothing()
|
||||||
|
.returning({
|
||||||
|
name: forkliftCompanies.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const err: DrizzleError = error;
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Error adding the server`,
|
||||||
|
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: `Server ${data[0]?.name} added`, data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
|
||||||
|
import { restrictToHosts } from "../../../../pkg/middleware/restrictToHosts.js";
|
||||||
|
import addCompany from "./addCompany.js";
|
||||||
|
import getCompanies from "./getCompanies.js";
|
||||||
|
import updateCompany from "./updateServer.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin"]),
|
||||||
|
getCompanies,
|
||||||
|
);
|
||||||
|
router.use("/", requireAuth("forklifts", ["systemAdmin", "admin"]), addCompany);
|
||||||
|
router.use(
|
||||||
|
"/",
|
||||||
|
requireAuth("forklifts", ["systemAdmin", "admin"]),
|
||||||
|
updateCompany,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
35
app/src/internal/forklifts/routes/companies/getCompanies.ts
Normal file
35
app/src/internal/forklifts/routes/companies/getCompanies.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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 { tryCatch } from "../../../../pkg/utils/tryCatch.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get("/", async (req: Request, res: Response) => {
|
||||||
|
const name = req.query.name;
|
||||||
|
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (name !== undefined) {
|
||||||
|
conditions.push(eq(forkliftCompanies.name, `${name}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
//conditions.push(eq(forkliftCompanies.active, true));
|
||||||
|
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db
|
||||||
|
.select()
|
||||||
|
.from(forkliftCompanies)
|
||||||
|
.where(and(...conditions))
|
||||||
|
.orderBy(asc(forkliftCompanies.name)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(400).json({ error: error });
|
||||||
|
}
|
||||||
|
res.status(200).json({ message: "Current Active Companies", data: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
103
app/src/internal/forklifts/routes/companies/updateServer.ts
Normal file
103
app/src/internal/forklifts/routes/companies/updateServer.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 { 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 company",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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?.name !== undefined) {
|
||||||
|
updates.name = req.body.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.body?.active !== undefined) {
|
||||||
|
updates.active = req.body.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.upd_user = req.user!.username || "lst_user";
|
||||||
|
updates.upd_date = sql`NOW()`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Object.keys(updates).length > 0) {
|
||||||
|
await db
|
||||||
|
.update(forkliftCompanies)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(forkliftCompanies.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 company to Main Server",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
log.info(
|
||||||
|
{ stack: data?.data },
|
||||||
|
"A new company 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 Server updated", error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
9
app/src/internal/forklifts/routes/routes.ts
Normal file
9
app/src/internal/forklifts/routes/routes.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Express, Request, Response } from "express";
|
||||||
|
import { requireAuth } from "../../../pkg/middleware/authMiddleware.js";
|
||||||
|
import companies from "./companies/companiesRoutes.js";
|
||||||
|
export const setupForkliftRoutes = (app: Express, basePath: string) => {
|
||||||
|
app.use(
|
||||||
|
basePath + "/api/forklifts/companies", // will pass bc system admin but this is just telling us we need this
|
||||||
|
companies,
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
import type { Express, Request, Response } from "express";
|
import type { Express, Request, Response } from "express";
|
||||||
import { setupAuthRoutes } from "../auth/routes/routes.js";
|
|
||||||
import { setupAdminRoutes } from "../admin/routes.js";
|
import { setupAdminRoutes } from "../admin/routes.js";
|
||||||
import { setupSystemRoutes } from "../system/routes.js";
|
import { setupAuthRoutes } from "../auth/routes/routes.js";
|
||||||
|
import { setupForkliftRoutes } from "../forklifts/routes/routes.js";
|
||||||
import { setupLogisticsRoutes } from "../logistics/routes.js";
|
import { setupLogisticsRoutes } from "../logistics/routes.js";
|
||||||
|
import { setupSystemRoutes } from "../system/routes.js";
|
||||||
|
|
||||||
export const setupRoutes = (app: Express, basePath: string) => {
|
export const setupRoutes = (app: Express, basePath: string) => {
|
||||||
// all routes
|
// all routes
|
||||||
setupAuthRoutes(app, basePath);
|
setupAuthRoutes(app, basePath);
|
||||||
setupAdminRoutes(app, basePath);
|
setupAdminRoutes(app, basePath);
|
||||||
setupSystemRoutes(app, basePath);
|
setupSystemRoutes(app, basePath);
|
||||||
setupLogisticsRoutes(app, basePath);
|
setupLogisticsRoutes(app, basePath);
|
||||||
|
setupForkliftRoutes(app, basePath);
|
||||||
|
|
||||||
// always try to go to the app weather we are in dev or in production.
|
// always try to go to the app weather we are in dev or in production.
|
||||||
app.get(basePath + "/", (req: Request, res: Response) => {
|
app.get(basePath + "/", (req: Request, res: Response) => {
|
||||||
res.redirect(basePath + "/app");
|
res.redirect(basePath + "/app");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fallback 404 handler
|
// Fallback 404 handler
|
||||||
app.use((req: Request, res: Response) => {
|
app.use((req: Request, res: Response) => {
|
||||||
res.status(404).json({ error: "Not Found" });
|
res.status(404).json({ error: "Not Found" });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
0
app/src/pkg/db/schema/forkliftComments.ts
Normal file
0
app/src/pkg/db/schema/forkliftComments.ts
Normal file
0
app/src/pkg/db/schema/forkliftHours.ts
Normal file
0
app/src/pkg/db/schema/forkliftHours.ts
Normal file
26
app/src/pkg/db/schema/forkliftLeaseCompanys.ts
Normal file
26
app/src/pkg/db/schema/forkliftLeaseCompanys.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const forkliftCompanies = pgTable("forklift_companies", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
name: text("name").notNull().unique(),
|
||||||
|
active: boolean("active").default(true),
|
||||||
|
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 selectForkliftCompanySchema =
|
||||||
|
createSelectSchema(forkliftCompanies);
|
||||||
|
|
||||||
|
export const insertForkliftCompanySchema = createInsertSchema(
|
||||||
|
forkliftCompanies,
|
||||||
|
).extend({
|
||||||
|
name: z.string().min(3),
|
||||||
|
// zipcode: z
|
||||||
|
// .string()
|
||||||
|
// .regex(/^\d{5}$/)
|
||||||
|
// .optional(),
|
||||||
|
});
|
||||||
13
app/src/pkg/db/schema/forkliftLeases.ts
Normal file
13
app/src/pkg/db/schema/forkliftLeases.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { date, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { createSelectSchema } from "drizzle-zod";
|
||||||
|
import { forkliftCompanies } from "./forkliftLeaseCompanys.js";
|
||||||
|
|
||||||
|
export const leases = pgTable("leases", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
leaseNumber: text("lease_number").notNull(),
|
||||||
|
companyId: uuid("company_id").references(() => forkliftCompanies.id),
|
||||||
|
startDate: date("start_date"),
|
||||||
|
endDate: date("end_date"),
|
||||||
|
leaseLink: text("lease_link"),
|
||||||
|
});
|
||||||
|
export const selectLeasesDataSchema = createSelectSchema(leases);
|
||||||
14
app/src/pkg/db/schema/forkliftLeasesInvoice.ts
Normal file
14
app/src/pkg/db/schema/forkliftLeasesInvoice.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { forklifts } from "./forklifts.js";
|
||||||
|
import { leaseInvoices } from "./leaseInvoices.js";
|
||||||
|
|
||||||
|
export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
invoiceId: uuid("invoice_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => leaseInvoices.id, { onDelete: "cascade" }),
|
||||||
|
forkliftId: uuid("forklift_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
|
||||||
|
amount: numeric("amount"), // optional: amount of invoice allocated to this lift
|
||||||
|
});
|
||||||
0
app/src/pkg/db/schema/forkliftRepairs.ts
Normal file
0
app/src/pkg/db/schema/forkliftRepairs.ts
Normal file
52
app/src/pkg/db/schema/forklifts.ts
Normal file
52
app/src/pkg/db/schema/forklifts.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
integer,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
serial,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { leases } from "./forkliftLeases.js";
|
||||||
|
import { serverData } from "./servers.js";
|
||||||
|
|
||||||
|
const status = pgEnum("forklift_status", [
|
||||||
|
"active",
|
||||||
|
"inactive",
|
||||||
|
"sold",
|
||||||
|
"retired",
|
||||||
|
"transferred",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const forklifts = pgTable("forklifts", {
|
||||||
|
forklift_id: uuid("forklift_id").defaultRandom().primaryKey(),
|
||||||
|
forkliftNumber: serial("forklift_number").notNull(),
|
||||||
|
serialNumber: text("serial_number").notNull(),
|
||||||
|
model: text("model").notNull(),
|
||||||
|
plant: text("plant")
|
||||||
|
.notNull()
|
||||||
|
.references(() => serverData.name, { onDelete: "set null" }), // what plant the forklift is in.
|
||||||
|
forkliftStatus: text("forklift_status").default("active"), //["active","inactive","sold","retired","transferred",]
|
||||||
|
glCode: integer("gl_code").notNull(), // this will be updated on the onconflift update so we dont do anything funny.
|
||||||
|
profitCenter: integer("profit_center").notNull(), // should be updated if its changing profit centers per request by the plant
|
||||||
|
manufacturer: text("manufacturer").notNull(),
|
||||||
|
manufacturerYear: text("manufacturer_year").notNull(),
|
||||||
|
engine: text("engine").notNull(),
|
||||||
|
batteryType: text("battery_type").notNull(),
|
||||||
|
leaseId: uuid("lease_id").references(() => leases.id, {
|
||||||
|
onDelete: "set null",
|
||||||
|
}),
|
||||||
|
dataPlate: text("data_plate"),
|
||||||
|
add_date: timestamp("add_date").defaultNow(),
|
||||||
|
add_user: text("add_user").default("LST"),
|
||||||
|
upd_date: timestamp("add_date").defaultNow(),
|
||||||
|
upd_user: text("add_user").default("LST"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const forkliftsSchema = createSelectSchema(forklifts);
|
||||||
|
export const newForkliftsSchema = createInsertSchema(forklifts);
|
||||||
|
|
||||||
|
export type Forklift = z.infer<typeof forkliftsSchema>;
|
||||||
|
export type NewNewForklift = z.infer<typeof newForkliftsSchema>;
|
||||||
19
app/src/pkg/db/schema/leaseInvoices.ts
Normal file
19
app/src/pkg/db/schema/leaseInvoices.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { date, numeric, pgTable, text, uuid } from "drizzle-orm/pg-core";
|
||||||
|
import { forkliftCompanies } from "./forkliftLeaseCompanys.js";
|
||||||
|
import { leases } from "./forkliftLeases.js";
|
||||||
|
import { forklifts } from "./forklifts.js";
|
||||||
|
|
||||||
|
export const leaseInvoices = pgTable("lease_invoices", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
leaseId: uuid("lease_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => leases.id, { onDelete: "cascade" }),
|
||||||
|
companyId: uuid("company_id").references(() => forkliftCompanies.id),
|
||||||
|
invoiceNumber: text("invoice_number").notNull(),
|
||||||
|
invoiceDate: date("invoice_date").notNull(),
|
||||||
|
forkliftId: uuid("forklift_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
|
||||||
|
totalAmount: numeric("total_amount"),
|
||||||
|
uploadedBy: text("uploaded_by"),
|
||||||
|
});
|
||||||
94
frontend/src/components/navBar/ForkliftSideBar.tsx
Normal file
94
frontend/src/components/navBar/ForkliftSideBar.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
Forklift,
|
||||||
|
Hourglass,
|
||||||
|
ReceiptText,
|
||||||
|
Wrench,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { userAccess } from "@/lib/authClient";
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "../ui/sidebar";
|
||||||
|
import type { Items } from "./SideBarNav";
|
||||||
|
|
||||||
|
export default function ForkliftSideBar() {
|
||||||
|
const items: Items[] = [
|
||||||
|
{
|
||||||
|
title: "Lease Companies",
|
||||||
|
url: "/lst/app/forklifts/companies",
|
||||||
|
icon: Building2,
|
||||||
|
role: ["systemAdmin", "admin"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Leases",
|
||||||
|
url: "/lst/app/admin/settings",
|
||||||
|
icon: ReceiptText,
|
||||||
|
role: ["systemAdmin", "admin"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Invoices",
|
||||||
|
url: "/lst/app/admin/settings",
|
||||||
|
icon: ReceiptText,
|
||||||
|
role: ["systemAdmin", "admin", "manager"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Repairs",
|
||||||
|
url: "/lst/app/admin/settings",
|
||||||
|
icon: Wrench,
|
||||||
|
role: ["systemAdmin", "admin", "manager"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Hours",
|
||||||
|
url: "/lst/app/admin/settings",
|
||||||
|
icon: Hourglass,
|
||||||
|
role: ["systemAdmin", "admin", "manager", "supervisor"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Forklifts",
|
||||||
|
url: "/lst/app/admin/modules",
|
||||||
|
icon: Forklift,
|
||||||
|
role: ["systemAdmin", "admin", "manager", "supervisor"],
|
||||||
|
module: "forklifts",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>Forklifts</SidebarGroupLabel>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<>
|
||||||
|
{userAccess(item.module, item.role) && item.active && (
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<Link to={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import { Link, useRouterState } from "@tanstack/react-router";
|
import { Link, useRouterState } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import NewCompanyForm from "@/routes/_app/_forklifts/-components/NewCompany";
|
||||||
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";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
import { Dialog, DialogContent } from "../ui/dialog";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -17,6 +20,7 @@ export default function Nav() {
|
|||||||
const logout = useLogout();
|
const logout = useLogout();
|
||||||
const router = useRouterState();
|
const router = useRouterState();
|
||||||
const currentPath = router.location.href;
|
const currentPath = router.location.href;
|
||||||
|
const [openDialog, setOpenDialog] = 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">
|
||||||
@@ -33,6 +37,37 @@ export default function Nav() {
|
|||||||
<Button className="m-1">
|
<Button className="m-1">
|
||||||
<Link to="/old">Old Version</Link>
|
<Link to="/old">Old Version</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
<div className="m-1">
|
||||||
|
{location.pathname.includes("forklifts") && (
|
||||||
|
<>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button>Forklifts</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuLabel>Forklift links</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={() => {
|
||||||
|
// just open the dialog when clicked
|
||||||
|
setOpenDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Company
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
{/* Dialog mounted outside the menu */}
|
||||||
|
{openDialog && (
|
||||||
|
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<NewCompanyForm setOpenDialog={setOpenDialog} />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{session ? (
|
{session ? (
|
||||||
<div className="m-1">
|
<div className="m-1">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { userAccess } from "../../lib/authClient";
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
import { type UserRoles, userAccess } from "@/lib/authClient";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -8,17 +9,32 @@ import {
|
|||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
} from "../ui/sidebar";
|
} from "../ui/sidebar";
|
||||||
import Admin from "./Admin";
|
import Admin from "./Admin";
|
||||||
|
import ForkliftSideBar from "./ForkliftSideBar";
|
||||||
import { Header } from "./Header";
|
import { Header } from "./Header";
|
||||||
|
|
||||||
|
export type Items = {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
role: UserRoles["role"][];
|
||||||
|
module: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export default function SideBarNav() {
|
export default function SideBarNav() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen">
|
<div className="flex min-h-screen">
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<Header />
|
<Header />
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
|
{userAccess(null, [
|
||||||
|
"systemAdmin",
|
||||||
|
"admin",
|
||||||
|
"manager",
|
||||||
|
"supervisor",
|
||||||
|
]) && <ForkliftSideBar />}
|
||||||
{userAccess(null, ["systemAdmin", "admin"]) && <Admin />}
|
{userAccess(null, ["systemAdmin", "admin"]) && <Admin />}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<Link to={"/changelog"}>Changelog</Link>
|
<Link to={"/changelog"}>Changelog</Link>
|
||||||
|
|||||||
@@ -16,16 +16,19 @@ import { Route as AppIndexRouteImport } from './routes/_app/index'
|
|||||||
import { Route as AppChangelogRouteImport } from './routes/_app/changelog'
|
import { Route as AppChangelogRouteImport } from './routes/_app/changelog'
|
||||||
import { Route as OldOldRouteRouteImport } from './routes/_old/old/route'
|
import { Route as OldOldRouteRouteImport } from './routes/_old/old/route'
|
||||||
import { Route as MobileMobileLayoutRouteRouteImport } from './routes/_mobile/_mobileLayout/route'
|
import { Route as MobileMobileLayoutRouteRouteImport } from './routes/_mobile/_mobileLayout/route'
|
||||||
|
import { Route as AppForkliftsRouteRouteImport } from './routes/_app/_forklifts/route'
|
||||||
import { Route as AppAdminLayoutRouteRouteImport } from './routes/_app/_adminLayout/route'
|
import { Route as AppAdminLayoutRouteRouteImport } from './routes/_app/_adminLayout/route'
|
||||||
import { Route as OldOldIndexRouteImport } from './routes/_old/old/index'
|
import { Route as OldOldIndexRouteImport } from './routes/_old/old/index'
|
||||||
import { Route as AppauthLoginRouteImport } from './routes/_app/(auth)/login'
|
import { Route as AppauthLoginRouteImport } from './routes/_app/(auth)/login'
|
||||||
import { Route as OldOldRfidIndexRouteImport } from './routes/_old/old/rfid/index'
|
import { Route as OldOldRfidIndexRouteImport } from './routes/_old/old/rfid/index'
|
||||||
import { Route as OldOldOcpIndexRouteImport } from './routes/_old/old/ocp/index'
|
import { Route as OldOldOcpIndexRouteImport } from './routes/_old/old/ocp/index'
|
||||||
import { Route as MobileMobileLayoutMIndexRouteImport } from './routes/_mobile/_mobileLayout/m/index'
|
import { Route as MobileMobileLayoutMIndexRouteImport } from './routes/_mobile/_mobileLayout/m/index'
|
||||||
|
import { Route as AppForkliftsForkliftsIndexRouteImport } from './routes/_app/_forklifts/forklifts/index'
|
||||||
import { Route as AppauthUserIndexRouteImport } from './routes/_app/(auth)/user/index'
|
import { Route as AppauthUserIndexRouteImport } from './routes/_app/(auth)/user/index'
|
||||||
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 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'
|
||||||
import { Route as AppauthUserSignupRouteImport } from './routes/_app/(auth)/user/signup'
|
import { Route as AppauthUserSignupRouteImport } from './routes/_app/(auth)/user/signup'
|
||||||
@@ -74,6 +77,10 @@ const MobileMobileLayoutRouteRoute = MobileMobileLayoutRouteRouteImport.update({
|
|||||||
id: '/_mobile/_mobileLayout',
|
id: '/_mobile/_mobileLayout',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AppForkliftsRouteRoute = AppForkliftsRouteRouteImport.update({
|
||||||
|
id: '/_forklifts',
|
||||||
|
getParentRoute: () => AppRouteRoute,
|
||||||
|
} as any)
|
||||||
const AppAdminLayoutRouteRoute = AppAdminLayoutRouteRouteImport.update({
|
const AppAdminLayoutRouteRoute = AppAdminLayoutRouteRouteImport.update({
|
||||||
id: '/_adminLayout',
|
id: '/_adminLayout',
|
||||||
getParentRoute: () => AppRouteRoute,
|
getParentRoute: () => AppRouteRoute,
|
||||||
@@ -109,6 +116,12 @@ const MobileMobileLayoutMIndexRoute =
|
|||||||
path: '/m/',
|
path: '/m/',
|
||||||
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AppForkliftsForkliftsIndexRoute =
|
||||||
|
AppForkliftsForkliftsIndexRouteImport.update({
|
||||||
|
id: '/forklifts/',
|
||||||
|
path: '/forklifts/',
|
||||||
|
getParentRoute: () => AppForkliftsRouteRoute,
|
||||||
|
} as any)
|
||||||
const AppauthUserIndexRoute = AppauthUserIndexRouteImport.update({
|
const AppauthUserIndexRoute = AppauthUserIndexRouteImport.update({
|
||||||
id: '/(auth)/user/',
|
id: '/(auth)/user/',
|
||||||
path: '/user/',
|
path: '/user/',
|
||||||
@@ -132,6 +145,12 @@ const MobileMobileLayoutMCyclecountsRoute =
|
|||||||
path: '/m/cyclecounts',
|
path: '/m/cyclecounts',
|
||||||
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
getParentRoute: () => MobileMobileLayoutRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AppForkliftsForkliftsCompaniesRoute =
|
||||||
|
AppForkliftsForkliftsCompaniesRouteImport.update({
|
||||||
|
id: '/forklifts/companies',
|
||||||
|
path: '/forklifts/companies',
|
||||||
|
getParentRoute: () => AppForkliftsRouteRoute,
|
||||||
|
} as any)
|
||||||
const AppAdminLayoutAdminServersRoute =
|
const AppAdminLayoutAdminServersRoute =
|
||||||
AppAdminLayoutAdminServersRouteImport.update({
|
AppAdminLayoutAdminServersRouteImport.update({
|
||||||
id: '/servers',
|
id: '/servers',
|
||||||
@@ -260,10 +279,12 @@ export interface FileRoutesByFullPath {
|
|||||||
'/user/signup': typeof AppauthUserSignupRoute
|
'/user/signup': typeof AppauthUserSignupRoute
|
||||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||||
|
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||||
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
||||||
'/user': typeof AppauthUserIndexRoute
|
'/user': typeof AppauthUserIndexRoute
|
||||||
|
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
|
||||||
'/m': typeof MobileMobileLayoutMIndexRoute
|
'/m': typeof MobileMobileLayoutMIndexRoute
|
||||||
'/old/ocp': typeof OldOldOcpIndexRoute
|
'/old/ocp': typeof OldOldOcpIndexRoute
|
||||||
'/old/rfid': typeof OldOldRfidIndexRoute
|
'/old/rfid': typeof OldOldRfidIndexRoute
|
||||||
@@ -292,10 +313,12 @@ export interface FileRoutesByTo {
|
|||||||
'/user/signup': typeof AppauthUserSignupRoute
|
'/user/signup': typeof AppauthUserSignupRoute
|
||||||
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
'/logistics/deliverySchedule': typeof ApplogisticsLogisticsDeliveryScheduleRoute
|
||||||
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
'/admin/servers': typeof AppAdminLayoutAdminServersRoute
|
||||||
|
'/forklifts/companies': typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
'/m/cyclecounts': typeof MobileMobileLayoutMCyclecountsRoute
|
||||||
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
'/m/delivery': typeof MobileMobileLayoutMDeliveryRoute
|
||||||
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
'/m/relocate': typeof MobileMobileLayoutMRelocateRoute
|
||||||
'/user': typeof AppauthUserIndexRoute
|
'/user': typeof AppauthUserIndexRoute
|
||||||
|
'/forklifts': typeof AppForkliftsForkliftsIndexRoute
|
||||||
'/m': typeof MobileMobileLayoutMIndexRoute
|
'/m': typeof MobileMobileLayoutMIndexRoute
|
||||||
'/old/ocp': typeof OldOldOcpIndexRoute
|
'/old/ocp': typeof OldOldOcpIndexRoute
|
||||||
'/old/rfid': typeof OldOldRfidIndexRoute
|
'/old/rfid': typeof OldOldRfidIndexRoute
|
||||||
@@ -317,6 +340,7 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/_app': typeof AppRouteRouteWithChildren
|
'/_app': typeof AppRouteRouteWithChildren
|
||||||
'/_app/_adminLayout': typeof AppAdminLayoutRouteRouteWithChildren
|
'/_app/_adminLayout': typeof AppAdminLayoutRouteRouteWithChildren
|
||||||
|
'/_app/_forklifts': typeof AppForkliftsRouteRouteWithChildren
|
||||||
'/_mobile/_mobileLayout': typeof MobileMobileLayoutRouteRouteWithChildren
|
'/_mobile/_mobileLayout': typeof MobileMobileLayoutRouteRouteWithChildren
|
||||||
'/_old/old': typeof OldOldRouteRouteWithChildren
|
'/_old/old': typeof OldOldRouteRouteWithChildren
|
||||||
'/_app/changelog': typeof AppChangelogRoute
|
'/_app/changelog': typeof AppChangelogRoute
|
||||||
@@ -331,10 +355,12 @@ export interface FileRoutesById {
|
|||||||
'/_app/(auth)/user/signup': typeof AppauthUserSignupRoute
|
'/_app/(auth)/user/signup': typeof AppauthUserSignupRoute
|
||||||
'/_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
|
||||||
'/_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
|
||||||
'/_app/(auth)/user/': typeof AppauthUserIndexRoute
|
'/_app/(auth)/user/': typeof AppauthUserIndexRoute
|
||||||
|
'/_app/_forklifts/forklifts/': typeof AppForkliftsForkliftsIndexRoute
|
||||||
'/_mobile/_mobileLayout/m/': typeof MobileMobileLayoutMIndexRoute
|
'/_mobile/_mobileLayout/m/': typeof MobileMobileLayoutMIndexRoute
|
||||||
'/_old/old/ocp/': typeof OldOldOcpIndexRoute
|
'/_old/old/ocp/': typeof OldOldOcpIndexRoute
|
||||||
'/_old/old/rfid/': typeof OldOldRfidIndexRoute
|
'/_old/old/rfid/': typeof OldOldRfidIndexRoute
|
||||||
@@ -366,10 +392,12 @@ export interface FileRouteTypes {
|
|||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
| '/logistics/deliverySchedule'
|
| '/logistics/deliverySchedule'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
|
| '/forklifts/companies'
|
||||||
| '/m/cyclecounts'
|
| '/m/cyclecounts'
|
||||||
| '/m/delivery'
|
| '/m/delivery'
|
||||||
| '/m/relocate'
|
| '/m/relocate'
|
||||||
| '/user'
|
| '/user'
|
||||||
|
| '/forklifts'
|
||||||
| '/m'
|
| '/m'
|
||||||
| '/old/ocp'
|
| '/old/ocp'
|
||||||
| '/old/rfid'
|
| '/old/rfid'
|
||||||
@@ -398,10 +426,12 @@ export interface FileRouteTypes {
|
|||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
| '/logistics/deliverySchedule'
|
| '/logistics/deliverySchedule'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
|
| '/forklifts/companies'
|
||||||
| '/m/cyclecounts'
|
| '/m/cyclecounts'
|
||||||
| '/m/delivery'
|
| '/m/delivery'
|
||||||
| '/m/relocate'
|
| '/m/relocate'
|
||||||
| '/user'
|
| '/user'
|
||||||
|
| '/forklifts'
|
||||||
| '/m'
|
| '/m'
|
||||||
| '/old/ocp'
|
| '/old/ocp'
|
||||||
| '/old/rfid'
|
| '/old/rfid'
|
||||||
@@ -422,6 +452,7 @@ export interface FileRouteTypes {
|
|||||||
| '__root__'
|
| '__root__'
|
||||||
| '/_app'
|
| '/_app'
|
||||||
| '/_app/_adminLayout'
|
| '/_app/_adminLayout'
|
||||||
|
| '/_app/_forklifts'
|
||||||
| '/_mobile/_mobileLayout'
|
| '/_mobile/_mobileLayout'
|
||||||
| '/_old/old'
|
| '/_old/old'
|
||||||
| '/_app/changelog'
|
| '/_app/changelog'
|
||||||
@@ -436,10 +467,12 @@ export interface FileRouteTypes {
|
|||||||
| '/_app/(auth)/user/signup'
|
| '/_app/(auth)/user/signup'
|
||||||
| '/_app/(logistics)/logistics/deliverySchedule'
|
| '/_app/(logistics)/logistics/deliverySchedule'
|
||||||
| '/_app/_adminLayout/admin/servers'
|
| '/_app/_adminLayout/admin/servers'
|
||||||
|
| '/_app/_forklifts/forklifts/companies'
|
||||||
| '/_mobile/_mobileLayout/m/cyclecounts'
|
| '/_mobile/_mobileLayout/m/cyclecounts'
|
||||||
| '/_mobile/_mobileLayout/m/delivery'
|
| '/_mobile/_mobileLayout/m/delivery'
|
||||||
| '/_mobile/_mobileLayout/m/relocate'
|
| '/_mobile/_mobileLayout/m/relocate'
|
||||||
| '/_app/(auth)/user/'
|
| '/_app/(auth)/user/'
|
||||||
|
| '/_app/_forklifts/forklifts/'
|
||||||
| '/_mobile/_mobileLayout/m/'
|
| '/_mobile/_mobileLayout/m/'
|
||||||
| '/_old/old/ocp/'
|
| '/_old/old/ocp/'
|
||||||
| '/_old/old/rfid/'
|
| '/_old/old/rfid/'
|
||||||
@@ -501,6 +534,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof MobileMobileLayoutRouteRouteImport
|
preLoaderRoute: typeof MobileMobileLayoutRouteRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/_app/_forklifts': {
|
||||||
|
id: '/_app/_forklifts'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof AppForkliftsRouteRouteImport
|
||||||
|
parentRoute: typeof AppRouteRoute
|
||||||
|
}
|
||||||
'/_app/_adminLayout': {
|
'/_app/_adminLayout': {
|
||||||
id: '/_app/_adminLayout'
|
id: '/_app/_adminLayout'
|
||||||
path: ''
|
path: ''
|
||||||
@@ -550,6 +590,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof MobileMobileLayoutMIndexRouteImport
|
preLoaderRoute: typeof MobileMobileLayoutMIndexRouteImport
|
||||||
parentRoute: typeof MobileMobileLayoutRouteRoute
|
parentRoute: typeof MobileMobileLayoutRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_app/_forklifts/forklifts/': {
|
||||||
|
id: '/_app/_forklifts/forklifts/'
|
||||||
|
path: '/forklifts'
|
||||||
|
fullPath: '/forklifts'
|
||||||
|
preLoaderRoute: typeof AppForkliftsForkliftsIndexRouteImport
|
||||||
|
parentRoute: typeof AppForkliftsRouteRoute
|
||||||
|
}
|
||||||
'/_app/(auth)/user/': {
|
'/_app/(auth)/user/': {
|
||||||
id: '/_app/(auth)/user/'
|
id: '/_app/(auth)/user/'
|
||||||
path: '/user'
|
path: '/user'
|
||||||
@@ -578,6 +625,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof MobileMobileLayoutMCyclecountsRouteImport
|
preLoaderRoute: typeof MobileMobileLayoutMCyclecountsRouteImport
|
||||||
parentRoute: typeof MobileMobileLayoutRouteRoute
|
parentRoute: typeof MobileMobileLayoutRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_app/_forklifts/forklifts/companies': {
|
||||||
|
id: '/_app/_forklifts/forklifts/companies'
|
||||||
|
path: '/forklifts/companies'
|
||||||
|
fullPath: '/forklifts/companies'
|
||||||
|
preLoaderRoute: typeof AppForkliftsForkliftsCompaniesRouteImport
|
||||||
|
parentRoute: typeof AppForkliftsRouteRoute
|
||||||
|
}
|
||||||
'/_app/_adminLayout/admin/servers': {
|
'/_app/_adminLayout/admin/servers': {
|
||||||
id: '/_app/_adminLayout/admin/servers'
|
id: '/_app/_adminLayout/admin/servers'
|
||||||
path: '/servers'
|
path: '/servers'
|
||||||
@@ -784,8 +838,22 @@ const AppAdminLayoutRouteRouteChildren: AppAdminLayoutRouteRouteChildren = {
|
|||||||
const AppAdminLayoutRouteRouteWithChildren =
|
const AppAdminLayoutRouteRouteWithChildren =
|
||||||
AppAdminLayoutRouteRoute._addFileChildren(AppAdminLayoutRouteRouteChildren)
|
AppAdminLayoutRouteRoute._addFileChildren(AppAdminLayoutRouteRouteChildren)
|
||||||
|
|
||||||
|
interface AppForkliftsRouteRouteChildren {
|
||||||
|
AppForkliftsForkliftsCompaniesRoute: typeof AppForkliftsForkliftsCompaniesRoute
|
||||||
|
AppForkliftsForkliftsIndexRoute: typeof AppForkliftsForkliftsIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppForkliftsRouteRouteChildren: AppForkliftsRouteRouteChildren = {
|
||||||
|
AppForkliftsForkliftsCompaniesRoute: AppForkliftsForkliftsCompaniesRoute,
|
||||||
|
AppForkliftsForkliftsIndexRoute: AppForkliftsForkliftsIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppForkliftsRouteRouteWithChildren =
|
||||||
|
AppForkliftsRouteRoute._addFileChildren(AppForkliftsRouteRouteChildren)
|
||||||
|
|
||||||
interface AppRouteRouteChildren {
|
interface AppRouteRouteChildren {
|
||||||
AppAdminLayoutRouteRoute: typeof AppAdminLayoutRouteRouteWithChildren
|
AppAdminLayoutRouteRoute: typeof AppAdminLayoutRouteRouteWithChildren
|
||||||
|
AppForkliftsRouteRoute: typeof AppForkliftsRouteRouteWithChildren
|
||||||
AppChangelogRoute: typeof AppChangelogRoute
|
AppChangelogRoute: typeof AppChangelogRoute
|
||||||
AppIndexRoute: typeof AppIndexRoute
|
AppIndexRoute: typeof AppIndexRoute
|
||||||
AppauthLoginRoute: typeof AppauthLoginRoute
|
AppauthLoginRoute: typeof AppauthLoginRoute
|
||||||
@@ -798,6 +866,7 @@ interface AppRouteRouteChildren {
|
|||||||
|
|
||||||
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||||
AppAdminLayoutRouteRoute: AppAdminLayoutRouteRouteWithChildren,
|
AppAdminLayoutRouteRoute: AppAdminLayoutRouteRouteWithChildren,
|
||||||
|
AppForkliftsRouteRoute: AppForkliftsRouteRouteWithChildren,
|
||||||
AppChangelogRoute: AppChangelogRoute,
|
AppChangelogRoute: AppChangelogRoute,
|
||||||
AppIndexRoute: AppIndexRoute,
|
AppIndexRoute: AppIndexRoute,
|
||||||
AppauthLoginRoute: AppauthLoginRoute,
|
AppauthLoginRoute: AppauthLoginRoute,
|
||||||
|
|||||||
@@ -159,16 +159,25 @@ function RouteComponent() {
|
|||||||
updateServer.mutate({ token, field, value: newValue });
|
updateServer.mutate({ token, field, value: newValue });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let submitting = false;
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
value={localValue}
|
value={localValue}
|
||||||
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||||
onBlur={(e) => handleSubmit(e.currentTarget.value.trim())}
|
onBlur={(e) => {
|
||||||
|
if (!submitting) {
|
||||||
|
submitting = true;
|
||||||
|
handleSubmit(e.currentTarget.value.trim());
|
||||||
|
setTimeout(() => (submitting = false), 100); // reset after slight delay
|
||||||
|
}
|
||||||
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSubmit(e.currentTarget.value.trim());
|
handleSubmit(e.currentTarget.value.trim());
|
||||||
e.currentTarget.blur(); // exit edit mode
|
e.currentTarget.blur(); // exit edit mode
|
||||||
|
setTimeout(() => (submitting = false), 100);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
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/getModules";
|
||||||
|
import { useAppForm } from "../../../../lib/formStuff";
|
||||||
|
|
||||||
|
export default function NewCompanyForm({
|
||||||
|
setOpenDialog,
|
||||||
|
}: {
|
||||||
|
setOpenDialog: any;
|
||||||
|
}) {
|
||||||
|
//const search = useSearch({ from: "/_app/(auth)/login" });
|
||||||
|
const { refetch } = useQuery(getCompanies());
|
||||||
|
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
try {
|
||||||
|
await axios.post("/lst/api/forklifts/companies", {
|
||||||
|
name: value.name,
|
||||||
|
});
|
||||||
|
form.reset();
|
||||||
|
setOpenDialog(false);
|
||||||
|
refetch();
|
||||||
|
toast.success(`${value.name} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Company</DialogTitle>
|
||||||
|
<DialogDescription>Add the new Leasing company</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form.AppField
|
||||||
|
name="name"
|
||||||
|
children={(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Company Name"
|
||||||
|
inputType="string"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="outline">Cancel</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
282
frontend/src/routes/_app/_forklifts/forklifts/companies.tsx
Normal file
282
frontend/src/routes/_app/_forklifts/forklifts/companies.tsx
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import {
|
||||||
|
createColumnHelper,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
type SortingState,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Activity, ArrowDown, ArrowUp } from "lucide-react";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { getCompanies } from "@/lib/querys/forklifts/getModules";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type Company = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
export const Route = createFileRoute("/_app/_forklifts/forklifts/companies")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateCompanyItem = async (
|
||||||
|
id: string,
|
||||||
|
data: Record<string, string | number | boolean | null>,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const res = await axios.patch(`/lst/api/forklifts/companies/${id}`, data, {
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
toast.success(`Company just updated`);
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Error in updating company");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const {
|
||||||
|
data: companyData = [],
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
} = useQuery(getCompanies());
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const columnHelper = createColumnHelper<Company>();
|
||||||
|
const submitting = useRef(false);
|
||||||
|
|
||||||
|
const updateCompany = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
id,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
field: string;
|
||||||
|
value: string | number | boolean | null;
|
||||||
|
}) => updateCompanyItem(id, { [field]: value }),
|
||||||
|
|
||||||
|
onSuccess: () => {
|
||||||
|
// refetch or update cache
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">Name</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row, getValue }) => {
|
||||||
|
const initialValue = String(getValue() ?? "");
|
||||||
|
const [localValue, setLocalValue] = useState(initialValue);
|
||||||
|
const id = row.original.id;
|
||||||
|
const field = "name";
|
||||||
|
|
||||||
|
useEffect(() => setLocalValue(initialValue), [initialValue]);
|
||||||
|
|
||||||
|
const handleSubmit = (newValue: string) => {
|
||||||
|
if (newValue !== initialValue) {
|
||||||
|
setLocalValue(newValue);
|
||||||
|
updateCompany.mutate({ id, field, value: newValue });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
value={localValue}
|
||||||
|
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (!submitting.current) {
|
||||||
|
submitting.current = true;
|
||||||
|
handleSubmit(e.currentTarget.value.trim());
|
||||||
|
setTimeout(() => (submitting.current = false), 100); // reset after slight delay
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
submitting.current = true;
|
||||||
|
handleSubmit(e.currentTarget.value.trim());
|
||||||
|
e.currentTarget.blur(); // will trigger blur, but we ignore it
|
||||||
|
setTimeout(() => (submitting.current = false), 100);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("active", {
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row gap-2">
|
||||||
|
<Activity />
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row, getValue }) => {
|
||||||
|
const active = getValue<boolean>();
|
||||||
|
const id = row.original.id;
|
||||||
|
const field = "active";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={active ? "true" : "false"}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const newValue = value === "true";
|
||||||
|
updateCompany.mutate({ id, field, value: newValue });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className={cn(
|
||||||
|
"w-[100px]",
|
||||||
|
active
|
||||||
|
? "border-green-500 text-green-600"
|
||||||
|
: "border-gray-400 text-gray-500",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="true">True</SelectItem>
|
||||||
|
<SelectItem value="false">False</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return <div className="m-auto">Loading user data</div>;
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
frontend/src/routes/_app/_forklifts/forklifts/index.tsx
Normal file
9
frontend/src/routes/_app/_forklifts/forklifts/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_app/_forklifts/forklifts/')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/_app/_forklifts/"!</div>
|
||||||
|
}
|
||||||
19
frontend/src/routes/_app/_forklifts/route.tsx
Normal file
19
frontend/src/routes/_app/_forklifts/route.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
|
import { checkUserAccess } from "@/lib/authClient";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_app/_forklifts")({
|
||||||
|
beforeLoad: async () =>
|
||||||
|
checkUserAccess({
|
||||||
|
allowedRoles: ["admin", "systemAdmin", "manager", "supervisor"],
|
||||||
|
moduleName: "forklifts", // optional
|
||||||
|
}),
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,12 +14,9 @@ import {
|
|||||||
} from "../../../../../lib/authClient";
|
} from "../../../../../lib/authClient";
|
||||||
|
|
||||||
import { AdminSideBar } from "./side-components/admin";
|
import { AdminSideBar } from "./side-components/admin";
|
||||||
import { EomSideBar } from "./side-components/eom";
|
|
||||||
import { ForkliftSideBar } from "./side-components/forklift";
|
|
||||||
import { Header } from "./side-components/header";
|
import { Header } from "./side-components/header";
|
||||||
import { LogisticsSideBar } from "./side-components/logistics";
|
import { LogisticsSideBar } from "./side-components/logistics";
|
||||||
import { ProductionSideBar } from "./side-components/production";
|
import { ProductionSideBar } from "./side-components/production";
|
||||||
import { QualitySideBar } from "./side-components/quality";
|
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const { session } = useAuth();
|
const { session } = useAuth();
|
||||||
@@ -36,9 +33,9 @@ export function AppSidebar() {
|
|||||||
<LogisticsSideBar user={session?.user as any} userRoles={userRoles} />
|
<LogisticsSideBar user={session?.user as any} userRoles={userRoles} />
|
||||||
{userAccess(null, ["systemAdmin"]) && (
|
{userAccess(null, ["systemAdmin"]) && (
|
||||||
<>
|
<>
|
||||||
<ForkliftSideBar />
|
{/* <ForkliftSideBar />
|
||||||
<EomSideBar />
|
<EomSideBar />
|
||||||
<QualitySideBar />
|
<QualitySideBar /> */}
|
||||||
<AdminSideBar />
|
<AdminSideBar />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
60
migrations/0028_brown_praxagora.sql
Normal file
60
migrations/0028_brown_praxagora.sql
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
CREATE TABLE "forklift_companies" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
CONSTRAINT "forklift_companies_name_unique" UNIQUE("name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "leases" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"lease_number" text NOT NULL,
|
||||||
|
"company_id" uuid,
|
||||||
|
"start_date" date,
|
||||||
|
"end_date" date,
|
||||||
|
"lease_link" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "lease_invoice_forklifts" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"invoice_id" uuid NOT NULL,
|
||||||
|
"forklift_id" uuid NOT NULL,
|
||||||
|
"amount" numeric
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "forklifts" (
|
||||||
|
"forklift_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"forklift_number" serial NOT NULL,
|
||||||
|
"serial_number" text NOT NULL,
|
||||||
|
"model" text NOT NULL,
|
||||||
|
"plant" text NOT NULL,
|
||||||
|
"forklift_status" "forklift_status" DEFAULT 'active',
|
||||||
|
"gl_code" integer NOT NULL,
|
||||||
|
"profit_center" integer NOT NULL,
|
||||||
|
"manufacturer" text NOT NULL,
|
||||||
|
"manufacturer_year" text NOT NULL,
|
||||||
|
"engine" text NOT NULL,
|
||||||
|
"battery_type" text NOT NULL,
|
||||||
|
"lease_id" uuid,
|
||||||
|
"data_plate" text,
|
||||||
|
"add_date" timestamp DEFAULT now(),
|
||||||
|
"add_user" text DEFAULT 'LST'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "lease_invoices" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"lease_id" uuid NOT NULL,
|
||||||
|
"company_id" uuid,
|
||||||
|
"invoice_number" text NOT NULL,
|
||||||
|
"invoice_date" date NOT NULL,
|
||||||
|
"forklift_id" uuid NOT NULL,
|
||||||
|
"total_amount" numeric,
|
||||||
|
"uploaded_by" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "leases" ADD CONSTRAINT "leases_company_id_forklift_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."forklift_companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoice_forklifts" ADD CONSTRAINT "lease_invoice_forklifts_invoice_id_lease_invoices_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "public"."lease_invoices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoice_forklifts" ADD CONSTRAINT "lease_invoice_forklifts_forklift_id_forklifts_forklift_id_fk" FOREIGN KEY ("forklift_id") REFERENCES "public"."forklifts"("forklift_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_plant_serverData_name_fk" FOREIGN KEY ("plant") REFERENCES "public"."serverData"("name") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_lease_id_leases_id_fk" FOREIGN KEY ("lease_id") REFERENCES "public"."leases"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" ADD CONSTRAINT "lease_invoices_lease_id_leases_id_fk" FOREIGN KEY ("lease_id") REFERENCES "public"."leases"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" ADD CONSTRAINT "lease_invoices_company_id_forklift_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."forklift_companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "lease_invoices" ADD CONSTRAINT "lease_invoices_forklift_id_forklifts_forklift_id_fk" FOREIGN KEY ("forklift_id") REFERENCES "public"."forklifts"("forklift_id") ON DELETE cascade ON UPDATE no action;
|
||||||
2
migrations/0029_rapid_wallow.sql
Normal file
2
migrations/0029_rapid_wallow.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "forklifts" ALTER COLUMN "forklift_status" SET DATA TYPE text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ALTER COLUMN "forklift_status" SET DEFAULT 'active';
|
||||||
1
migrations/0030_flawless_talisman.sql
Normal file
1
migrations/0030_flawless_talisman.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CREATE UNIQUE INDEX "plant_name" ON "serverData" USING btree ("name");
|
||||||
4
migrations/0031_red_bug.sql
Normal file
4
migrations/0031_red_bug.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE "forklifts" DROP CONSTRAINT "forklifts_plant_serverData_name_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
DROP INDEX "plant_name";--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_plant_serverData_server_id_fk" FOREIGN KEY ("plant") REFERENCES "public"."serverData"("server_id") ON DELETE set null ON UPDATE no action;
|
||||||
4
migrations/0032_eminent_james_howlett.sql
Normal file
4
migrations/0032_eminent_james_howlett.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE "forklifts" DROP CONSTRAINT "forklifts_plant_serverData_server_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "serverData" ADD CONSTRAINT "serverData_name_unique" UNIQUE("name");
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_plant_serverData_name_fk" FOREIGN KEY ("plant") REFERENCES "public"."serverData"("name") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
3
migrations/0033_luxuriant_marvel_zombies.sql
Normal file
3
migrations/0033_luxuriant_marvel_zombies.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "forklifts" DROP CONSTRAINT "forklifts_plant_serverData_name_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_plant_serverData_server_id_fk" FOREIGN KEY ("plant") REFERENCES "public"."serverData"("server_id") ON DELETE set null ON UPDATE no action;
|
||||||
3
migrations/0034_living_war_machine.sql
Normal file
3
migrations/0034_living_war_machine.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "forklifts" DROP CONSTRAINT "forklifts_plant_serverData_server_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_plant_serverData_name_fk" FOREIGN KEY ("plant") REFERENCES "public"."serverData"("name") ON DELETE set null ON UPDATE no action;
|
||||||
3
migrations/0035_overrated_ben_parker.sql
Normal file
3
migrations/0035_overrated_ben_parker.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "forklift_companies" ADD COLUMN "active" boolean DEFAULT true;--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklift_companies" ADD COLUMN "add_date" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklift_companies" ADD COLUMN "add_user" text DEFAULT 'LST';
|
||||||
2
migrations/0036_sticky_brood.sql
Normal file
2
migrations/0036_sticky_brood.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "forklift_companies" ADD COLUMN "upd_date" timestamp DEFAULT now();--> statement-breakpoint
|
||||||
|
ALTER TABLE "forklift_companies" ADD COLUMN "upd_user" text DEFAULT 'LST';
|
||||||
2138
migrations/meta/0028_snapshot.json
Normal file
2138
migrations/meta/0028_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2137
migrations/meta/0029_snapshot.json
Normal file
2137
migrations/meta/0029_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2152
migrations/meta/0030_snapshot.json
Normal file
2152
migrations/meta/0030_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2137
migrations/meta/0031_snapshot.json
Normal file
2137
migrations/meta/0031_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2145
migrations/meta/0032_snapshot.json
Normal file
2145
migrations/meta/0032_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2145
migrations/meta/0033_snapshot.json
Normal file
2145
migrations/meta/0033_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2145
migrations/meta/0034_snapshot.json
Normal file
2145
migrations/meta/0034_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2166
migrations/meta/0035_snapshot.json
Normal file
2166
migrations/meta/0035_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2180
migrations/meta/0036_snapshot.json
Normal file
2180
migrations/meta/0036_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -197,6 +197,69 @@
|
|||||||
"when": 1761938877395,
|
"when": 1761938877395,
|
||||||
"tag": "0027_special_harpoon",
|
"tag": "0027_special_harpoon",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 28,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762110963295,
|
||||||
|
"tag": "0028_brown_praxagora",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 29,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111060465,
|
||||||
|
"tag": "0029_rapid_wallow",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 30,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111106977,
|
||||||
|
"tag": "0030_flawless_talisman",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111262895,
|
||||||
|
"tag": "0031_red_bug",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 32,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111388336,
|
||||||
|
"tag": "0032_eminent_james_howlett",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 33,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111567029,
|
||||||
|
"tag": "0033_luxuriant_marvel_zombies",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 34,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762111614977,
|
||||||
|
"tag": "0034_living_war_machine",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762112218732,
|
||||||
|
"tag": "0035_overrated_ben_parker",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 36,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1762112909957,
|
||||||
|
"tag": "0036_sticky_brood",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user