feat(forklifts): added backend forklift stuff and frontend companies

This commit is contained in:
2025-11-02 16:16:35 -06:00
parent a6cc17ccb1
commit 50cde2d8d2
52 changed files with 20619 additions and 32 deletions

View File

@@ -159,16 +159,25 @@ function RouteComponent() {
updateServer.mutate({ token, field, value: newValue });
}
};
let submitting = false;
return (
<Input
value={localValue}
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) => {
if (e.key === "Enter") {
e.preventDefault();
handleSubmit(e.currentTarget.value.trim());
e.currentTarget.blur(); // exit edit mode
setTimeout(() => (submitting = false), 100);
}
}}
/>

View File

@@ -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>
</>
);
}

View 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>
);
}

View 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>
}

View 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>
);
}

View File

@@ -14,12 +14,9 @@ import {
} from "../../../../../lib/authClient";
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 { LogisticsSideBar } from "./side-components/logistics";
import { ProductionSideBar } from "./side-components/production";
import { QualitySideBar } from "./side-components/quality";
export function AppSidebar() {
const { session } = useAuth();
@@ -36,9 +33,9 @@ export function AppSidebar() {
<LogisticsSideBar user={session?.user as any} userRoles={userRoles} />
{userAccess(null, ["systemAdmin"]) && (
<>
<ForkliftSideBar />
{/* <ForkliftSideBar />
<EomSideBar />
<QualitySideBar />
<QualitySideBar /> */}
<AdminSideBar />
</>
)}