feat(invoice form): added new invoice form
This commit is contained in:
264
frontend/src/routes/_app/_forklifts/-components/NewInvoice.tsx
Normal file
264
frontend/src/routes/_app/_forklifts/-components/NewInvoice.tsx
Normal file
@@ -0,0 +1,264 @@
|
||||
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 { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useAppForm } from "@/lib/formStuff";
|
||||
import { getCompanies } from "@/lib/querys/forklifts/getCompanies";
|
||||
import { getInvoices } from "@/lib/querys/forklifts/getInvoices";
|
||||
|
||||
type CompanyData = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function NewInvoice({
|
||||
setOpenInvoiceDialog,
|
||||
}: {
|
||||
setOpenInvoiceDialog: any;
|
||||
}) {
|
||||
const { refetch } = useQuery(getInvoices());
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
companyName: "",
|
||||
leaseId: "",
|
||||
invoiceNumber: "",
|
||||
invoiceDate: "",
|
||||
totalAmount: "",
|
||||
forklifts: [{ forklift_id: "", serialNumber: "", amount: "" }],
|
||||
},
|
||||
onSubmit: async ({ value }) => {
|
||||
const updatedForklifts = value.forklifts.map(
|
||||
({ serialNumber, ...rest }) => rest,
|
||||
);
|
||||
const postData = {
|
||||
leaseId: value.leaseId,
|
||||
invoiceNumber: value.invoiceNumber,
|
||||
invoiceDate: format(value.invoiceDate, "MM/dd/yyyy"),
|
||||
totalAmount: value.totalAmount,
|
||||
forklifts: updatedForklifts,
|
||||
};
|
||||
console.log(postData);
|
||||
try {
|
||||
await axios.post("/lst/api/forklifts/invoices", postData);
|
||||
form.reset();
|
||||
setOpenInvoiceDialog(false);
|
||||
refetch();
|
||||
toast.success(`${value.invoiceNumber} was just created `);
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
console.log(error);
|
||||
// @ts-ignore
|
||||
if (!error.response.data.success) {
|
||||
// @ts-ignore
|
||||
toast.error(<span>{error?.response?.data.error}</span>);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
toast.error(error?.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { data: c, isLoading: ce } = useQuery(getCompanies());
|
||||
|
||||
let companyName = form.getFieldValue("companyName");
|
||||
|
||||
const { data: l = [], refetch: lf } = useQuery({
|
||||
queryKey: ["lease", companyName],
|
||||
queryFn: async () => {
|
||||
//if (!companyName) return [];
|
||||
const { data } = await axios.get(
|
||||
`/lst/api/forklifts/leases?companyId=${companyName}`,
|
||||
);
|
||||
return data.data;
|
||||
},
|
||||
enabled: !!companyName, // only run if nameId has value
|
||||
});
|
||||
|
||||
if (ce) return <div>Loading Companies</div>;
|
||||
|
||||
// remap the companies to fit out select field
|
||||
const companyMap = c.map((i: CompanyData) => {
|
||||
return { value: i.id, label: i.name };
|
||||
});
|
||||
|
||||
const leaseMap = l.map((i: any) => {
|
||||
return { value: i.id, label: i.leaseNumber };
|
||||
});
|
||||
|
||||
const onValueChange = (value: string) => {
|
||||
companyName = value;
|
||||
lf();
|
||||
form.setFieldValue("leaseId", "");
|
||||
};
|
||||
|
||||
let forkliftArray = [];
|
||||
const onLeaseChange = (value: string) => {
|
||||
const selectedLease = l.find((lease: any) => lease.id === value);
|
||||
|
||||
forkliftArray =
|
||||
selectedLease?.forklifts.length > 0
|
||||
? selectedLease.forklifts.map((f: any) => ({
|
||||
forklift_id: f.forklift_id,
|
||||
amount: 0,
|
||||
serialNumber: f.serialNumber,
|
||||
}))
|
||||
: [
|
||||
// {
|
||||
// forklift_id: "missing",
|
||||
// amount: 0,
|
||||
// serialNumber: "Missing forklift",
|
||||
// },
|
||||
];
|
||||
form.setFieldValue("forklifts", forkliftArray);
|
||||
};
|
||||
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="companyName"
|
||||
listeners={{
|
||||
onChange: ({ value }) => {
|
||||
onValueChange(value);
|
||||
},
|
||||
}}
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Company"
|
||||
placeholder="Companies"
|
||||
options={companyMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="leaseId"
|
||||
listeners={{
|
||||
onChange: ({ value }) => {
|
||||
onLeaseChange(value);
|
||||
},
|
||||
}}
|
||||
children={(field) => (
|
||||
<field.SelectField
|
||||
label="Select Lease"
|
||||
placeholder="LeaseNumber"
|
||||
options={leaseMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="w-5/6 m-2">
|
||||
<form.AppField
|
||||
name="invoiceNumber"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Enter Invoice Number"
|
||||
inputType="string"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-5/6 m-2">
|
||||
<form.AppField
|
||||
name="totalAmount"
|
||||
children={(field) => (
|
||||
<field.InputField
|
||||
label="Enter Invoice Amount"
|
||||
inputType="decimal"
|
||||
required={true}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<form.AppField
|
||||
name="invoiceDate"
|
||||
children={(field) => (
|
||||
<field.DateField label="Invoice Date" required={true} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<hr className="mt-2 mb-2" />
|
||||
{/* Dynamic forklift section */}
|
||||
<div className="space-y-3 mt-4">
|
||||
<form.Field
|
||||
name="forklifts"
|
||||
mode="array"
|
||||
children={(field) => (
|
||||
<>
|
||||
<Label>Forklifts</Label>
|
||||
{field.state.value.map((fx, index) => (
|
||||
<form.AppField
|
||||
key={fx.forklift_id}
|
||||
name={`forklifts[${index}].amount`}
|
||||
children={(subField) => (
|
||||
<div className="flex flex-row gap-2">
|
||||
<Label htmlFor={subField.name}>{fx.serialNumber}</Label>
|
||||
<Input
|
||||
className="w-1/4"
|
||||
id={subField.name}
|
||||
value={subField.state.value ?? ""}
|
||||
onChange={(e) => {
|
||||
// update this subfield’s amount
|
||||
subField.handleChange(e.target.value);
|
||||
|
||||
// if you also want to store the forklift_id with it
|
||||
field.handleChange(
|
||||
field.state.value.map((val, i) =>
|
||||
i === index
|
||||
? { ...val, amount: e.target.value }
|
||||
: val,
|
||||
),
|
||||
);
|
||||
}}
|
||||
onBlur={subField.handleBlur}
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Map out the input filed based on the forklift id */}
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">Submit</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -108,11 +108,13 @@ export default function NewLeaseForm({
|
||||
<div className="flex flex-row gap-2 mt-2 mb-2">
|
||||
<form.AppField
|
||||
name="startDate"
|
||||
children={(field) => <field.DateField label="Start Date" />}
|
||||
children={(field) => (
|
||||
<field.DateField label="Start Date" required />
|
||||
)}
|
||||
/>
|
||||
<form.AppField
|
||||
name="endDate"
|
||||
children={(field) => <field.DateField label="End Date" />}
|
||||
children={(field) => <field.DateField label="End Date" required />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
115
frontend/src/routes/_app/_forklifts/forklifts/invoices.tsx
Normal file
115
frontend/src/routes/_app/_forklifts/forklifts/invoices.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
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 { Button } from "@/components/ui/button";
|
||||
import { getInvoices } from "@/lib/querys/forklifts/getInvoices";
|
||||
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
|
||||
|
||||
type Invoices = {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
invoiceDate: Date;
|
||||
totalAmount: string;
|
||||
add_date: Date;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/_app/_forklifts/forklifts/invoices")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: invoiceData = [], isLoading } = useQuery(getInvoices());
|
||||
|
||||
const columnHelper = createColumnHelper<Invoices>();
|
||||
//const submitting = useRef(false);
|
||||
console.log(invoiceData);
|
||||
const columns = [
|
||||
columnHelper.accessor("invoiceNumber", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Invoice Number</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("totalAmount", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Total Amount</span>
|
||||
{column.getIsSorted() === "asc" ? (
|
||||
<ArrowUp className="ml-2 h-4 w-4" />
|
||||
) : (
|
||||
<ArrowDown className="ml-2 h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("invoiceDate", {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
<span className="flex flex-row gap-2">Invoice 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("add_date", {
|
||||
// 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>;
|
||||
// },
|
||||
//}),
|
||||
];
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="m-auto">Loading user data</div>;
|
||||
}
|
||||
return <TableNoExpand data={invoiceData} columns={columns} />;
|
||||
}
|
||||
@@ -65,7 +65,6 @@ let lotColumns = [
|
||||
];
|
||||
export default function Lots() {
|
||||
const { data, isError, isLoading } = useQuery(getlots());
|
||||
|
||||
const { session } = useAuth();
|
||||
//const { settings } = useSettingStore();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ManualPrintForm() {
|
||||
const { settings } = useSettingStore();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const [printing, setPrinting] = useState(false);
|
||||
// const serverPort = settings.filter((n) => n.name === "serverPort")[0]?.value;
|
||||
// const serverUrl = `http://${server}:${serverPort}`;
|
||||
|
||||
@@ -59,6 +59,7 @@ export default function ManualPrintForm() {
|
||||
// toast.success(`A new label was sent to printer: ${lot.PrinterName} for line ${lot.MachineDescription} `);
|
||||
const logdataUrl = `/lst/old/api/ocp/manuallabellog`;
|
||||
setIsSubmitting(true);
|
||||
setPrinting(true);
|
||||
axios
|
||||
.post(logdataUrl, logData, {})
|
||||
.then((d) => {
|
||||
@@ -71,11 +72,13 @@ export default function ManualPrintForm() {
|
||||
reset();
|
||||
setOpen(false);
|
||||
setIsSubmitting(false);
|
||||
setPrinting(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.response.status === 500) {
|
||||
toast.error(`Internal Server error please try again.`);
|
||||
setIsSubmitting(false);
|
||||
setPrinting(false);
|
||||
return { sucess: false };
|
||||
}
|
||||
|
||||
@@ -97,6 +100,7 @@ export default function ManualPrintForm() {
|
||||
const closeForm = () => {
|
||||
reset();
|
||||
setOpen(false);
|
||||
setPrinting(false);
|
||||
};
|
||||
return (
|
||||
<Dialog
|
||||
@@ -106,13 +110,14 @@ export default function ManualPrintForm() {
|
||||
reset();
|
||||
}
|
||||
setOpen(isOpen);
|
||||
setPrinting(true);
|
||||
// toast.message("Model was something", {
|
||||
// description: isOpen ? "Modal is open" : "Modal is closed",
|
||||
// });
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Button variant="outline" size="icon" disabled={printing}>
|
||||
<Tag className="h-[16px] w-[16px]" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
Reference in New Issue
Block a user