feat(dm): added import and exports plus first custom for dayton
This commit is contained in:
80
frontend/src/components/logistics/dm/DMButtons.tsx
Normal file
80
frontend/src/components/logistics/dm/DMButtons.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import OrderImport from "./OrderImport";
|
||||||
|
import StandardOrderTemplate from "./StandardOrderTemplate";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import StandardForecastTemplate from "./StandardForecastTemplate";
|
||||||
|
import ForecastImport from "./ForecastImport";
|
||||||
|
import { useSettingStore } from "@/lib/store/useSettings";
|
||||||
|
|
||||||
|
export default function DMButtons() {
|
||||||
|
const { settings } = useSettingStore();
|
||||||
|
|
||||||
|
const plantToken = settings.filter((n) => n.name === "plantToken");
|
||||||
|
console.log(plantToken);
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row-reverse">
|
||||||
|
<div className="m-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button>Standard DM</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuLabel>
|
||||||
|
Standard templates and imports
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<StandardOrderTemplate />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<OrderImport
|
||||||
|
fileType={"standard"}
|
||||||
|
name={"Standard Order Import"}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<StandardForecastTemplate />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<ForecastImport
|
||||||
|
fileType={"standard"}
|
||||||
|
name={"Standard Forecast Import"}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
{plantToken[0]?.value === "usday1" && (
|
||||||
|
<div className="m-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button>Dayton Customs</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuLabel>
|
||||||
|
Custom import templates
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<OrderImport
|
||||||
|
fileType={"abbott"}
|
||||||
|
name={"Abbott truck list"}
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
frontend/src/components/logistics/dm/ForecastImport.tsx
Normal file
66
frontend/src/components/logistics/dm/ForecastImport.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export default function ForecastImport(props: any) {
|
||||||
|
const fileInputRef: any = useRef(null);
|
||||||
|
const [posting, setPosting] = useState(false);
|
||||||
|
const token = localStorage.getItem("auth_token");
|
||||||
|
//const [fileType, setFileType] = useState("");
|
||||||
|
const importOrders = async (e: any) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
toast.error("Missing file please try again");
|
||||||
|
setPosting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the form data with the correct fileType
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("postOrders", e.target.files[0]);
|
||||||
|
formData.append("fileType", props.fileType); // extra field
|
||||||
|
// console.log(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
"/api/logistics/postforecastin",
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log("Upload successful:", response.data);
|
||||||
|
toast.success(
|
||||||
|
"File Uploaded, please validate processing in alplaprod 2.0"
|
||||||
|
);
|
||||||
|
setPosting(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Upload failed");
|
||||||
|
}
|
||||||
|
setPosting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
setPosting(true);
|
||||||
|
fileInputRef.current.click();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button onClick={handleButtonClick} disabled={posting}>
|
||||||
|
{props.name}
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx, .xls, .xlsm"
|
||||||
|
ref={fileInputRef}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={importOrders}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
frontend/src/components/logistics/dm/OrderImport.tsx
Normal file
66
frontend/src/components/logistics/dm/OrderImport.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export default function OrderImport(props: any) {
|
||||||
|
const fileInputRef: any = useRef(null);
|
||||||
|
const [posting, setPosting] = useState(false);
|
||||||
|
const token = localStorage.getItem("auth_token");
|
||||||
|
//const [fileType, setFileType] = useState("");
|
||||||
|
const importOrders = async (e: any) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) {
|
||||||
|
toast.error("Missing file please try again");
|
||||||
|
setPosting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the form data with the correct fileType
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("postOrders", e.target.files[0]);
|
||||||
|
formData.append("fileType", props.fileType); // extra field
|
||||||
|
// console.log(formData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
"/api/logistics/postbulkorders",
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log("Upload successful:", response.data);
|
||||||
|
toast.success(
|
||||||
|
"File Uploaded, please validate processing in alplaprod 2.0"
|
||||||
|
);
|
||||||
|
setPosting(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("Upload failed");
|
||||||
|
}
|
||||||
|
setPosting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
setPosting(true);
|
||||||
|
fileInputRef.current.click();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button onClick={handleButtonClick} disabled={posting}>
|
||||||
|
{props.name}
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx, .xls, .xlsm"
|
||||||
|
ref={fileInputRef}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={importOrders}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import axios from "axios";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export default function StandardForecastTemplate() {
|
||||||
|
const [template, setTemplate] = useState(false);
|
||||||
|
const getTemplate = async () => {
|
||||||
|
setTemplate(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`/api/logistics/bulkforcasttemplate`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([res.data], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `ForecastTemplate-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
toast.success(`Forecast template`);
|
||||||
|
setTemplate(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTemplate(false);
|
||||||
|
toast.error("There was an error getting the template");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Button onClick={getTemplate} disabled={template}>
|
||||||
|
Standard Forecast Template
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import axios from "axios";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export default function StandardOrderTemplate() {
|
||||||
|
const [template, setTemplate] = useState(false);
|
||||||
|
const getTemplate = async () => {
|
||||||
|
setTemplate(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`/api/logistics/bulkorderstemplate`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([res.data], {
|
||||||
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `BulkOrderTemplate-${format(new Date(Date.now()), "M-d-yyyy")}.xlsx`; // You can make this dynamic
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
toast.success(`Bulk Order template`);
|
||||||
|
setTemplate(false);
|
||||||
|
} catch (error) {
|
||||||
|
setTemplate(false);
|
||||||
|
toast.error("There was an error getting the tempalte");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Button onClick={getTemplate} disabled={template}>
|
||||||
|
Standard Order Template
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
129
frontend/src/components/logistics/dm/dmPage.tsx
Normal file
129
frontend/src/components/logistics/dm/dmPage.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { LstCard } from "@/components/extendedUI/LstCard";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
|
export default function DmPage() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<LstCard className="w-1/2">
|
||||||
|
<div className="w-96">
|
||||||
|
<h4 className="text-center underline text-2xl">
|
||||||
|
Simple instructions for creating/updating orders
|
||||||
|
</h4>
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="list-disc mr-2">
|
||||||
|
<li>
|
||||||
|
Download the standard template if you have not yet done
|
||||||
|
so, top right click standard, then template.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Add in the orders like you see in the example below.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
When updating orders you are required to have the
|
||||||
|
customerOrderNumber, customerLineNumber, and
|
||||||
|
customerReleaseNumber. Quatity and dates can change.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Once you have all the orders enters click the upload
|
||||||
|
button on the top right
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Separator className="my-4" />
|
||||||
|
<div className="m-5">
|
||||||
|
<h4 className="text-center underline text-2xl">
|
||||||
|
Some notes to consider
|
||||||
|
</h4>
|
||||||
|
<ul className="list-disc mr-2">
|
||||||
|
<li>
|
||||||
|
No longer need to add in the invoice id, we take the
|
||||||
|
default one.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
You can have as many customers you want in the file
|
||||||
|
and in any order.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Orders created Manually, can not be updated with
|
||||||
|
this process.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
If you desire you can send up to 1000 orders at one
|
||||||
|
time... or more....
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Customer mappings can be added, this means that you
|
||||||
|
can upload your customer file and it will parse
|
||||||
|
onces mapped, you need to request this.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Custom imports will be at the top right under
|
||||||
|
custom, here you will just upload the customer file
|
||||||
|
once mapped.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Separator className="my-4" />
|
||||||
|
<p className="text-center underline text-2xl">Example order</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<img
|
||||||
|
src="/imgs/ordersInExample.png"
|
||||||
|
alt="orders in example"
|
||||||
|
className="w-[680px] h-[280px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</LstCard>
|
||||||
|
<LstCard className="w-1/2">
|
||||||
|
<div className="w-96">
|
||||||
|
<h4 className="text-center underline text-2xl">
|
||||||
|
Simple instructions for creating forecast
|
||||||
|
</h4>
|
||||||
|
<Separator className="my-4" />
|
||||||
|
</div>
|
||||||
|
<div className="m-5">
|
||||||
|
<ul className="list-disc mr-2">
|
||||||
|
<li>
|
||||||
|
Download the template if you have not yet done so.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Add in the forecast like you see in the example
|
||||||
|
below.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Once you have all the forecast enters click the
|
||||||
|
upload button on the top right
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Separator className="my-4" />
|
||||||
|
<div className="m-5">
|
||||||
|
<h4 className="text-center underline text-2xl">
|
||||||
|
Some notes to consider.
|
||||||
|
</h4>
|
||||||
|
<ul className="list-disc mr-2">
|
||||||
|
<li>You can only use one customer at a time.</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
If you desire you can send up to 1000 orders at one
|
||||||
|
time...
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Separator className="my-4" />
|
||||||
|
<p className="text-center underline text-2xl">
|
||||||
|
Example forecast
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<img
|
||||||
|
src="/imgs/exampleforecast.png"
|
||||||
|
alt="orders in example"
|
||||||
|
className="w-[480px] h-[280px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</LstCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import DmPage from "@/components/logistics/dm/dmPage";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/(logistics)/dm/")({
|
export const Route = createFileRoute("/(logistics)/dm/")({
|
||||||
@@ -19,5 +20,9 @@ export const Route = createFileRoute("/(logistics)/dm/")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/(logistics)/dm/"!</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<DmPage />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { useSession } from "@/hooks/useSession";
|
|||||||
import { useLogout } from "@/hooks/useLogout";
|
import { useLogout } from "@/hooks/useLogout";
|
||||||
import ExportInventoryData from "@/components/logistics/warehouse/ExportInventoryData";
|
import ExportInventoryData from "@/components/logistics/warehouse/ExportInventoryData";
|
||||||
import { AddCards } from "@/components/dashboard/AddCards";
|
import { AddCards } from "@/components/dashboard/AddCards";
|
||||||
|
import DMButtons from "@/components/logistics/dm/DMButtons";
|
||||||
//import { AddCards } from "@/components/dashboard/AddCards";
|
//import { AddCards } from "@/components/dashboard/AddCards";
|
||||||
|
|
||||||
// same as the layout
|
// same as the layout
|
||||||
@@ -44,12 +45,19 @@ export const Route = createRootRoute({
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<nav className="flex justify-end w-full shadow ">
|
<nav className="flex justify-end w-full shadow ">
|
||||||
<div className="m-2 flex flex-row">
|
<div className="m-2 flex flex-row">
|
||||||
|
{/* Inventory section */}
|
||||||
{location.pathname === "/" && (
|
{location.pathname === "/" && (
|
||||||
<div className="m-auto pr-2 flex flex-row gap-2">
|
<div className="m-auto pr-2 flex flex-row gap-2">
|
||||||
<ExportInventoryData />
|
<ExportInventoryData />
|
||||||
<AddCards />
|
<AddCards />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Demand mgt section this should also include plant token stuff */}
|
||||||
|
{location.pathname === "/dm" && (
|
||||||
|
<div className="m-auto pr-2 flex flex-row gap-2">
|
||||||
|
<DMButtons />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="m-1">
|
<div className="m-1">
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ const newSubModules = [
|
|||||||
subSubModule: [],
|
subSubModule: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bulk orders",
|
name: "Demand Management",
|
||||||
moduleName: "logistics",
|
moduleName: "logistics",
|
||||||
description: "",
|
description: "Bulk order and Forecast imports",
|
||||||
link: "#",
|
link: "/dm",
|
||||||
icon: "Truck",
|
icon: "Truck",
|
||||||
role: ["systemAdmin"],
|
role: ["systemAdmin"],
|
||||||
active: false,
|
active: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user