feat(dm): migrated all the dm topics
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 4m26s
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 4m26s
This commit is contained in:
53
frontend/src/components/Sidebar/LogisticsBar.tsx
Normal file
53
frontend/src/components/Sidebar/LogisticsBar.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Truck } from "lucide-react";
|
||||
import { getSettings } from "../../lib/queries/getSettings";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "../ui/sidebar";
|
||||
|
||||
export default function LogisticsSidebar({ session }: any) {
|
||||
const { setOpen } = useSidebar();
|
||||
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||
const items = [
|
||||
{
|
||||
title: "Demand Management",
|
||||
url: "/logistics/dm",
|
||||
icon: Truck,
|
||||
role: ["systemAdmin", "admin", "warehouse", "transport"],
|
||||
module: "logistics",
|
||||
active:
|
||||
!isLoading &&
|
||||
settings.filter((n: any) => n.name === "demandManagement")[0].active,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Logistics</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<div key={item.title}>
|
||||
{item.role.includes(session.user.role) && item.active && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link to={item.url} onClick={() => setOpen(false)}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { getSettings } from "../../lib/queries/getSettings";
|
||||
import { permissionQuery } from "../../lib/queries/permsCheck";
|
||||
import AdminSidebar from "./AdminBar";
|
||||
import DocBar from "./DocBar";
|
||||
import LogisticsSidebar from "./LogisticsBar";
|
||||
import MobileBar from "./MobileBar";
|
||||
import TransportationBar from "./TransportationBar";
|
||||
import WarehouseBar from "./Warhouse";
|
||||
@@ -27,6 +28,12 @@ export function AppSidebar() {
|
||||
openDock: ["read"],
|
||||
}),
|
||||
);
|
||||
|
||||
const { data: canReadWarehouse = false } = useQuery(
|
||||
permissionQuery({
|
||||
warehouse: ["update"],
|
||||
}),
|
||||
);
|
||||
const { setOpen } = useSidebar();
|
||||
|
||||
// const { data: canReadWarehouse = false } = useQuery(
|
||||
@@ -47,9 +54,11 @@ export function AppSidebar() {
|
||||
<SidebarContent>
|
||||
<DocBar />
|
||||
{!isLoading &&
|
||||
canReadWarehouse &&
|
||||
settings.filter((n: any) => n.name === "mobile")[0].active && (
|
||||
<MobileBar />
|
||||
)}
|
||||
{!isLoading && session && <LogisticsSidebar session={session} />}
|
||||
|
||||
{!isLoading &&
|
||||
settings.filter((n: any) => n.name === "opendock_sync")[0]
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Route as ApidocsRouteImport } from './routes/apidocs'
|
||||
import { Route as AboutRouteImport } from './routes/about'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||
import { Route as LogisticsDmRouteImport } from './routes/logistics/dm'
|
||||
import { Route as DocsDatamartRouteImport } from './routes/docs/datamart'
|
||||
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
||||
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
||||
@@ -63,6 +64,11 @@ const DocsIndexRoute = DocsIndexRouteImport.update({
|
||||
path: '/docs/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const LogisticsDmRoute = LogisticsDmRouteImport.update({
|
||||
id: '/logistics/dm',
|
||||
path: '/logistics/dm',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const DocsDatamartRoute = DocsDatamartRouteImport.update({
|
||||
id: '/docs/datamart',
|
||||
path: '/docs/datamart',
|
||||
@@ -169,6 +175,7 @@ export interface FileRoutesByFullPath {
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
'/docs/datamart': typeof DocsDatamartRoute
|
||||
'/logistics/dm': typeof LogisticsDmRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
'/user/profile': typeof authUserProfileRoute
|
||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
@@ -194,6 +201,7 @@ export interface FileRoutesByTo {
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
'/docs/datamart': typeof DocsDatamartRoute
|
||||
'/logistics/dm': typeof LogisticsDmRoute
|
||||
'/docs': typeof DocsIndexRoute
|
||||
'/user/profile': typeof authUserProfileRoute
|
||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
@@ -220,6 +228,7 @@ export interface FileRoutesById {
|
||||
'/admin/users': typeof AdminUsersRoute
|
||||
'/docs/$': typeof DocsSplatRoute
|
||||
'/docs/datamart': typeof DocsDatamartRoute
|
||||
'/logistics/dm': typeof LogisticsDmRoute
|
||||
'/docs/': typeof DocsIndexRoute
|
||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
||||
@@ -247,6 +256,7 @@ export interface FileRouteTypes {
|
||||
| '/admin/users'
|
||||
| '/docs/$'
|
||||
| '/docs/datamart'
|
||||
| '/logistics/dm'
|
||||
| '/docs/'
|
||||
| '/user/profile'
|
||||
| '/user/resetpassword'
|
||||
@@ -272,6 +282,7 @@ export interface FileRouteTypes {
|
||||
| '/admin/users'
|
||||
| '/docs/$'
|
||||
| '/docs/datamart'
|
||||
| '/logistics/dm'
|
||||
| '/docs'
|
||||
| '/user/profile'
|
||||
| '/user/resetpassword'
|
||||
@@ -297,6 +308,7 @@ export interface FileRouteTypes {
|
||||
| '/admin/users'
|
||||
| '/docs/$'
|
||||
| '/docs/datamart'
|
||||
| '/logistics/dm'
|
||||
| '/docs/'
|
||||
| '/(auth)/user/profile'
|
||||
| '/(auth)/user/resetpassword'
|
||||
@@ -323,6 +335,7 @@ export interface RootRouteChildren {
|
||||
AdminUsersRoute: typeof AdminUsersRoute
|
||||
DocsSplatRoute: typeof DocsSplatRoute
|
||||
DocsDatamartRoute: typeof DocsDatamartRoute
|
||||
LogisticsDmRoute: typeof LogisticsDmRoute
|
||||
DocsIndexRoute: typeof DocsIndexRoute
|
||||
authUserProfileRoute: typeof authUserProfileRoute
|
||||
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
||||
@@ -378,6 +391,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof DocsIndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/logistics/dm': {
|
||||
id: '/logistics/dm'
|
||||
path: '/logistics/dm'
|
||||
fullPath: '/logistics/dm'
|
||||
preLoaderRoute: typeof LogisticsDmRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/docs/datamart': {
|
||||
id: '/docs/datamart'
|
||||
path: '/docs/datamart'
|
||||
@@ -515,6 +535,7 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
AdminUsersRoute: AdminUsersRoute,
|
||||
DocsSplatRoute: DocsSplatRoute,
|
||||
DocsDatamartRoute: DocsDatamartRoute,
|
||||
LogisticsDmRoute: LogisticsDmRoute,
|
||||
DocsIndexRoute: DocsIndexRoute,
|
||||
authUserProfileRoute: authUserProfileRoute,
|
||||
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
||||
|
||||
@@ -248,13 +248,13 @@ function RouteComponent() {
|
||||
};
|
||||
//console.log(logs);
|
||||
return (
|
||||
<div className="flex flex-col gap-1 max-w-7xl">
|
||||
<div className="flex flex-col gap-1 w-7xl">
|
||||
<div className="flex gap-1 justify-end">
|
||||
<Button onClick={triggerBuild}>Trigger Build</Button>
|
||||
<Button onClick={() => clearRoom()}>Clear Logs</Button>
|
||||
</div>
|
||||
<div className="flex gap-1 w-full">
|
||||
<div className="w-full">
|
||||
<div className="w-3/4">
|
||||
<Suspense fallback={<SkellyTable />}>
|
||||
<ServerTable />
|
||||
</Suspense>
|
||||
|
||||
@@ -178,10 +178,10 @@ function SettingsTableCard() {
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 w-7xl flex flex-col justify-center">
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold">Settings</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<h1 className="text-2xl font-semibold text-center">Settings</h1>
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Manage your settings and related data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
146
frontend/src/routes/logistics/-components/dm.ForecastUpload.tsx
Normal file
146
frontend/src/routes/logistics/-components/dm.ForecastUpload.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Separator } from "../../../components/ui/separator";
|
||||
import { api } from "../../../lib/apiHelper";
|
||||
import { useSession } from "../../../lib/auth-client";
|
||||
|
||||
export default function ForecastUpload({
|
||||
server,
|
||||
responseData,
|
||||
}: {
|
||||
server: string;
|
||||
responseData: any;
|
||||
}) {
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [posting, setPosting] = useState(false);
|
||||
const [selectedFileType, setSelectedFileType] = useState<string>("");
|
||||
const { data: session } = useSession();
|
||||
|
||||
const importOrders = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
console.log("clicked import");
|
||||
responseData([]);
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
toast.error("Missing or no file was selected please try again");
|
||||
setPosting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the form data with the correct fileType
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("fileType", selectedFileType); // extra field
|
||||
formData.append("username", `${session?.user.username}`);
|
||||
|
||||
toast.success("Import started.");
|
||||
try {
|
||||
const response = await api.post("/logistics/dm/forecast", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
validateStatus: (status) => status < 500,
|
||||
});
|
||||
//console.log("Upload successful:", response.data);
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
setPosting(false);
|
||||
|
||||
if (response.status === 200) {
|
||||
toast.success(response?.data?.message);
|
||||
|
||||
responseData(response.data.data);
|
||||
} else {
|
||||
toast.error(response?.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Upload failed");
|
||||
}
|
||||
setPosting(false);
|
||||
};
|
||||
|
||||
const handleButtonClick = (type: string) => {
|
||||
setPosting(true);
|
||||
|
||||
const handleFocus = () => {
|
||||
setTimeout(() => {
|
||||
if (!fileInputRef.current?.files?.length) {
|
||||
setPosting(false);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
window.addEventListener("focus", handleFocus, { once: true });
|
||||
setSelectedFileType(type);
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const ForecastButton = ({ name, type }: { name: string; type: string }) => {
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => handleButtonClick(type)} disabled={posting}>
|
||||
{name}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// For plants that basically use the same import set like this so we dont have weird looking cards
|
||||
const pngForecast = ["usiow1", "usiow2", "usksc1"];
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Card>
|
||||
<CardTitle>
|
||||
<p className="text-center">Forecast</p>
|
||||
</CardTitle>
|
||||
<CardDescription className="w-64 p-2 ">
|
||||
<p className="text-xs">
|
||||
When clicking on one of the below options you will need to upload
|
||||
the respective file to be processed to 2.0
|
||||
</p>
|
||||
<Separator />
|
||||
</CardDescription>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<ForecastButton name={"Standard"} type={"standard"} />
|
||||
{server === "usday1" ||
|
||||
(server.includes("test") && (
|
||||
<ForecastButton
|
||||
name={"Energizer Forecast"}
|
||||
type={"energizer"}
|
||||
/>
|
||||
))}
|
||||
|
||||
{pngForecast.includes(server) ||
|
||||
(server.includes("test") && (
|
||||
<ForecastButton name={"PnG"} type={"pg"} />
|
||||
))}
|
||||
|
||||
{server === "usflo1" ||
|
||||
(server.includes("test") && (
|
||||
<ForecastButton name={"VMI Import"} type={"loreal"} />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".xlsx, .xls, .xlsm"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={importOrders}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
139
frontend/src/routes/logistics/-components/dm.OrdersUpload.tsx
Normal file
139
frontend/src/routes/logistics/-components/dm.OrdersUpload.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Separator } from "../../../components/ui/separator";
|
||||
import { api } from "../../../lib/apiHelper";
|
||||
import { useSession } from "../../../lib/auth-client";
|
||||
|
||||
export default function OrdersUpload({
|
||||
server,
|
||||
responseData,
|
||||
}: {
|
||||
server: string;
|
||||
responseData: any;
|
||||
}) {
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [posting, setPosting] = useState(false);
|
||||
const [selectedFileType, setSelectedFileType] = useState<string>("");
|
||||
const { data: session } = useSession();
|
||||
|
||||
const importOrders = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
responseData([]);
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) {
|
||||
toast.error("Missing or no file was selected please try again");
|
||||
setPosting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// create the form data with the correct fileType
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("fileType", selectedFileType); // extra field
|
||||
formData.append("username", `${session?.user.username}`);
|
||||
|
||||
toast.success("Import started.");
|
||||
try {
|
||||
const response = await api.post("/logistics/dm/orders", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
validateStatus: (status) => status < 500,
|
||||
});
|
||||
//console.log("Upload successful:", response.data);
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
setPosting(false);
|
||||
|
||||
if (response.status === 200) {
|
||||
toast.success(response?.data?.message);
|
||||
//console.log(response.data);
|
||||
responseData(response.data.data);
|
||||
} else {
|
||||
toast.error(response?.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Upload failed");
|
||||
}
|
||||
setPosting(false);
|
||||
};
|
||||
|
||||
const handleButtonClick = (type: string) => {
|
||||
setPosting(true);
|
||||
|
||||
const handleFocus = () => {
|
||||
setTimeout(() => {
|
||||
if (!fileInputRef.current?.files?.length) {
|
||||
setPosting(false);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
window.addEventListener("focus", handleFocus, { once: true });
|
||||
setSelectedFileType(type);
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const OrderButton = ({ name, type }: { name: string; type: string }) => {
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => handleButtonClick(type)} disabled={posting}>
|
||||
{name}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Card>
|
||||
<CardTitle>
|
||||
<p className="text-center">Orders</p>
|
||||
</CardTitle>
|
||||
<CardDescription className="w-64 p-2 ">
|
||||
<p className="text-xs">
|
||||
When clicking on one of the below options you will need to upload
|
||||
the respective file to be processed to 2.0
|
||||
</p>
|
||||
<Separator />
|
||||
</CardDescription>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<OrderButton name={"Standard"} type={"standard"} />
|
||||
{server === "usweb1" ||
|
||||
(server.includes("test") && (
|
||||
<OrderButton name={"SCJ Orders"} type={"scj"} />
|
||||
))}
|
||||
{server === "usday1" ||
|
||||
(server.includes("test") && (
|
||||
<>
|
||||
<OrderButton name={"Abbott"} type={"abbott"} />
|
||||
<OrderButton
|
||||
name={"Energizer Truck List"}
|
||||
type={"energizer"}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".xlsx, .xls, .xlsm"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={importOrders}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
72
frontend/src/routes/logistics/-components/dm.Templates.tsx
Normal file
72
frontend/src/routes/logistics/-components/dm.Templates.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { format } from "date-fns";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { Separator } from "../../../components/ui/separator";
|
||||
import { api } from "../../../lib/apiHelper";
|
||||
|
||||
export default function Templates() {
|
||||
const [template, setTemplate] = useState(false);
|
||||
|
||||
const getTemplate = async (type: "orders" | "forecast") => {
|
||||
setTemplate(true);
|
||||
try {
|
||||
const res = await api.get(`/logistics/dm/template?filename=${type}`, {
|
||||
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 = `Bulk${type.charAt(0).toUpperCase() + type.slice(1)}_Template-${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(`Template created for ${type}`);
|
||||
setTemplate(false);
|
||||
} catch {
|
||||
setTemplate(false);
|
||||
toast.error("There was an error getting the template");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<CardTitle>
|
||||
<p className="text-center">Templates</p>
|
||||
</CardTitle>
|
||||
<CardDescription className="w-64 p-2 ">
|
||||
<p>
|
||||
Clicking one of the template's below will generate an excel file you
|
||||
can fill out and re-upload, using the standard orders or standard
|
||||
forecast button
|
||||
</p>
|
||||
<Separator />
|
||||
</CardDescription>
|
||||
<CardContent>
|
||||
<div className="flex flex-rol justify-center gap-2">
|
||||
<Button onClick={() => getTemplate("orders")} disabled={template}>
|
||||
Orders
|
||||
</Button>
|
||||
<Button onClick={() => getTemplate("forecast")} disabled={template}>
|
||||
Forecast
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
127
frontend/src/routes/logistics/dm.tsx
Normal file
127
frontend/src/routes/logistics/dm.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { useState } from "react";
|
||||
import { Separator } from "../../components/ui/separator";
|
||||
import LstTable from "../../lib/tableStuff/LstTable";
|
||||
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||
import { runtimeConfig } from "../../lib/umami.utils";
|
||||
import ForecastUpload from "./-components/dm.ForecastUpload";
|
||||
import OrdersUpload from "./-components/dm.OrdersUpload";
|
||||
import Templates from "./-components/dm.Templates";
|
||||
|
||||
export const Route = createFileRoute("/logistics/dm")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const server = runtimeConfig.server;
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
const [uploadInfo, setUploadInfo] = useState<any>([]);
|
||||
|
||||
console.log(uploadInfo?.data?.orders);
|
||||
const columns = [
|
||||
columnHelper.accessor("customerId", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Customer ID"
|
||||
searchable={true}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <>{i.getValue()}</>,
|
||||
}),
|
||||
columnHelper.accessor("customerOrderNo", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Customer Order Number"
|
||||
searchable={true}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <>{i.getValue()}</>,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.positions?.[0]?.customerLineItemNo, {
|
||||
id: "customerLineItemNo",
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Customer Line Item No"
|
||||
searchable={true}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <>{i.getValue()}</>,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.positions?.[0]?.deliveryDate, {
|
||||
id: "deliveryDate",
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader
|
||||
column={column}
|
||||
title="Delivery date"
|
||||
searchable={true}
|
||||
/>
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <>{i.getValue()}</>,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.positions?.[0]?.quantity, {
|
||||
id: "quantity",
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Quantity" searchable={true} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <>{i.getValue()}</>,
|
||||
}),
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<div className="w-7x1 mt-2 flex flex-row gap-2 justify-center">
|
||||
<div>
|
||||
<Templates />
|
||||
</div>
|
||||
<div>
|
||||
<ForecastUpload server={server} responseData={setUploadInfo} />
|
||||
</div>
|
||||
<div>
|
||||
<OrdersUpload server={server} responseData={setUploadInfo} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<p>
|
||||
As a reminder, All forecast and orders, uploaded will always show
|
||||
processed as good or error, you will still need to go into AlplaPROD
|
||||
2.0 and validate the file processed all orders with no errors.
|
||||
</p>
|
||||
<p>
|
||||
This is due to AlplaPROD basically saying it has accepted your file
|
||||
only.
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<p className="text-sm">
|
||||
The info in the table below is what was processed and sent over to
|
||||
AlplaPROD, if releases were in any other state than planned they would
|
||||
be skipped and not show up below.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
We only pass back the data you sent in no releases you will need to
|
||||
validate this on your own.
|
||||
</p>
|
||||
<Separator className="m-2" />
|
||||
<p className="text-center">
|
||||
The below table will show orders only forecast will be added in later
|
||||
</p>
|
||||
<div className="w-7xl flex justify-center mt-3">
|
||||
<LstTable
|
||||
data={uploadInfo?.data?.orders ?? []}
|
||||
columns={columns}
|
||||
pageSize={50}
|
||||
className="max-h-96 "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user