From fda0719d87b57510b09a944f77945d5d92d75d40 Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Wed, 5 Mar 2025 20:15:38 -0600 Subject: [PATCH] feat(eom): frame work added in for eom --- database/schema/eom.ts | 22 +++ frontend/package.json | 4 + frontend/src/components/eom/EomPage.tsx | 131 ++++++++++++++++++ frontend/src/components/eom/KFP.tsx | 68 +++++++++ .../components/layout/side-components/eom.tsx | 8 +- frontend/src/components/ui/calendar.tsx | 73 ++++++++++ frontend/src/components/ui/passwordInput.tsx | 52 +++++++ frontend/src/components/ui/popover.tsx | 46 ++++++ frontend/src/components/ui/tabs.tsx | 64 +++++++++ frontend/src/routeTree.gen.ts | 89 +++++++++++- frontend/src/routes/_eom.tsx | 13 ++ frontend/src/routes/_eom/article/$av.tsx | 125 +++++++++++++++++ frontend/src/routes/_eom/eom.tsx | 14 ++ server/services/eom/eomService.ts | 5 + 14 files changed, 708 insertions(+), 6 deletions(-) create mode 100644 database/schema/eom.ts create mode 100644 frontend/src/components/eom/EomPage.tsx create mode 100644 frontend/src/components/eom/KFP.tsx create mode 100644 frontend/src/components/ui/calendar.tsx create mode 100644 frontend/src/components/ui/passwordInput.tsx create mode 100644 frontend/src/components/ui/popover.tsx create mode 100644 frontend/src/components/ui/tabs.tsx create mode 100644 frontend/src/routes/_eom.tsx create mode 100644 frontend/src/routes/_eom/article/$av.tsx create mode 100644 frontend/src/routes/_eom/eom.tsx create mode 100644 server/services/eom/eomService.ts diff --git a/database/schema/eom.ts b/database/schema/eom.ts new file mode 100644 index 0000000..5cb026c --- /dev/null +++ b/database/schema/eom.ts @@ -0,0 +1,22 @@ +import {date, pgTable, text} from "drizzle-orm/pg-core"; +import {createSelectSchema} from "drizzle-zod"; + +export const eom = pgTable( + "eom", + { + eomMonth: date().notNull(), + article: text().notNull(), + articleDescription: text().notNull(), + } + // (table) => [ + // // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`), + // uniqueIndex("role_name").on(table.name), + // ] +); + +// Schema for inserting a user - can be used to validate API requests +// export const insertRolesSchema = createInsertSchema(roles, { +// name: z.string().min(3, {message: "Role name must be more than 3 letters"}), +// }); +// Schema for selecting a Expenses - can be used to validate API responses +export const selectRolesSchema = createSelectSchema(eom); diff --git a/frontend/package.json b/frontend/package.json index bc3d63c..9074f6e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,8 +18,10 @@ "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.9", "@tanstack/react-query": "^5.66.9", @@ -27,6 +29,7 @@ "@tanstack/react-table": "^8.21.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "date-fns": "^4.1.0", "dotenv": "^16.4.7", "hono": "^4.7.2", "js-cookie": "^3.0.5", @@ -34,6 +37,7 @@ "lucide-react": "^0.476.0", "next-themes": "^0.4.4", "react": "^19.0.0", + "react-day-picker": "^8.10.1", "react-dom": "^19.0.0", "react-grid-layout": "^1.5.0", "react-hook-form": "^7.54.2", diff --git a/frontend/src/components/eom/EomPage.tsx b/frontend/src/components/eom/EomPage.tsx new file mode 100644 index 0000000..144ccb1 --- /dev/null +++ b/frontend/src/components/eom/EomPage.tsx @@ -0,0 +1,131 @@ +import {useSessionStore} from "@/lib/store/sessionStore"; +import {LstCard} from "../extendedUI/LstCard"; +import {Tabs, TabsContent, TabsList, TabsTrigger} from "../ui/tabs"; +import {useModuleStore} from "@/lib/store/useModuleStore"; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "../ui/table"; +import {Skeleton} from "../ui/skeleton"; + +import {Link, useRouter} from "@tanstack/react-router"; +import {Popover, PopoverContent, PopoverTrigger} from "../ui/popover"; +import {Button} from "../ui/button"; +import {cn} from "@/lib/utils"; +import {CalendarIcon} from "lucide-react"; +import {format, startOfMonth} from "date-fns"; +import {Calendar} from "../ui/calendar"; +import {useState} from "react"; +import {toast} from "sonner"; +import KFP from "./KFP"; + +export default function EomPage() { + const {modules} = useModuleStore(); + const {user} = useSessionStore(); + const router = useRouter(); + const [date, setDate] = useState(); + + if (!user) { + router.navigate({to: "/"}); + } + const eomMod = modules.filter((m) => m.name === "eom"); + // the users current role for eom is? + const role: any = user?.roles.filter((r) => r.module_id === eomMod[0].module_id) || ""; + + const tabs = [ + {key: "kfp", label: "Key Figures", roles: ["admin", "systemAdmin"], content: }, + {key: "fg", label: "Finished Goods", roles: ["admin", "systemAdmin"], content: }, + {key: "mm", label: "Main Material", roles: ["admin", "systemAdmin"], content: }, + {key: "mb", label: "Master Batch", roles: ["admin", "systemAdmin"], content: }, + {key: "ab", label: "Additive", roles: ["admin", "systemAdmin"], content: }, + {key: "pp", label: "Purchased Preforms", roles: ["admin", "systemAdmin"], content: }, + {key: "pre", label: "Preforms", roles: ["admin", "systemAdmin"], content: }, + {key: "pkg", label: "Packaging", roles: ["admin", "systemAdmin"], content: }, + {key: "ui", label: "Undefined Items", roles: ["admin"], content: }, + ]; + return ( +
+
+ + + + + + + + +
+ +
+
+ + + + {tabs.map((tab) => { + if (tab.roles.includes(role[0].role)) + return {tab.label}; + })} + + {tabs.map((tab) => { + if (tab.roles.includes(role[0].role)) + return {tab.content}; + })} + +
+ ); +} + +function DummyContent() { + return ( + + + + + Av + Description + Material Type + Waste + Loss / Gain $$ + + + + + {Array(10) + .fill(0) + .map((_, i) => ( + + + + {i} + + + + + + + + + + + + + + + {/* + + */} + + ))} + +
+
+ ); +} diff --git a/frontend/src/components/eom/KFP.tsx b/frontend/src/components/eom/KFP.tsx new file mode 100644 index 0000000..ccd220f --- /dev/null +++ b/frontend/src/components/eom/KFP.tsx @@ -0,0 +1,68 @@ +import {useModuleStore} from "@/lib/store/useModuleStore"; +import {LstCard} from "../extendedUI/LstCard"; +import {CardHeader} from "../ui/card"; +import {Tabs, TabsContent, TabsList, TabsTrigger} from "../ui/tabs"; +import {useSessionStore} from "@/lib/store/sessionStore"; + +export default function KFP() { + const {modules} = useModuleStore(); + const {user} = useSessionStore(); + const eomMod = modules.filter((m) => m.name === "eom"); + // the users current role for eom is? + const role: any = user?.roles.filter((r) => r.module_id === eomMod[0].module_id) || ""; + const tabs = [ + {key: "mat", label: "Materials", roles: ["admin", "systemAdmin"], content: }, + {key: "sbm", label: "Stretch Blow", roles: ["admin", "systemAdmin"]}, + ]; + return ( +
+ + + {tabs.map((tab) => { + if (tab.roles.includes(role[0].role)) + return {tab.label}; + })} + + {tabs.map((tab) => { + if (tab.roles.includes(role[0].role)) + return ( + + {tab.content} + + ); + })} + +
+ ); +} + +function Materials() { + return ( +
+
+ + Resin + + + MasterBatch + + + Additive + + + Dose cups / Spigots / Spouts + +
+
+ + Pre-Emis Material Consistency report + +
+
+ + Regrind Report in kg + +
+
+ ); +} diff --git a/frontend/src/components/layout/side-components/eom.tsx b/frontend/src/components/layout/side-components/eom.tsx index 309f3b5..33489a9 100644 --- a/frontend/src/components/layout/side-components/eom.tsx +++ b/frontend/src/components/layout/side-components/eom.tsx @@ -1,4 +1,4 @@ -import {Printer} from "lucide-react"; +import {FileText} from "lucide-react"; import { SidebarGroup, SidebarGroupContent, @@ -10,9 +10,9 @@ import { const items = [ { - title: "Qaulity Request", - url: "#", - icon: Printer, + title: "End Of Month", + url: "/eom", + icon: FileText, }, ]; diff --git a/frontend/src/components/ui/calendar.tsx b/frontend/src/components/ui/calendar.tsx new file mode 100644 index 0000000..3976ece --- /dev/null +++ b/frontend/src/components/ui/calendar.tsx @@ -0,0 +1,73 @@ +import * as React from "react" +import { ChevronLeft, ChevronRight } from "lucide-react" +import { DayPicker } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: React.ComponentProps) { + return ( + .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" + ), + day: cn( + buttonVariants({ variant: "ghost" }), + "size-8 p-0 font-normal aria-selected:opacity-100" + ), + day_range_start: + "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground", + day_range_end: + "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground aria-selected:text-muted-foreground", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", + ...classNames, + }} + components={{ + IconLeft: ({ className, ...props }) => ( + + ), + IconRight: ({ className, ...props }) => ( + + ), + }} + {...props} + /> + ) +} + +export { Calendar } diff --git a/frontend/src/components/ui/passwordInput.tsx b/frontend/src/components/ui/passwordInput.tsx new file mode 100644 index 0000000..c65cfdc --- /dev/null +++ b/frontend/src/components/ui/passwordInput.tsx @@ -0,0 +1,52 @@ +"use client"; + +import * as React from "react"; +import {EyeIcon, EyeOffIcon} from "lucide-react"; + +import {Button} from "@/components/ui/button"; +import {Input} from "@/components/ui/input"; +import {cn} from "@/lib/utils"; + +const PasswordInput = React.forwardRef(({className, ...props}, ref) => { + const [showPassword, setShowPassword] = React.useState(false); + const disabled = props.value === "" || props.value === undefined || props.disabled; + + return ( +
+ + + + {/* hides browsers password toggles */} + +
+ ); +}); +PasswordInput.displayName = "PasswordInput"; + +export {PasswordInput}; diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx new file mode 100644 index 0000000..83728a8 --- /dev/null +++ b/frontend/src/components/ui/popover.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx new file mode 100644 index 0000000..7096b65 --- /dev/null +++ b/frontend/src/components/ui/tabs.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 248e49d..999dade 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -13,14 +13,17 @@ import { Route as rootRoute } from './routes/__root' import { Route as LoginImport } from './routes/login' import { Route as AboutImport } from './routes/about' +import { Route as EomImport } from './routes/_eom' import { Route as AuthImport } from './routes/_auth' import { Route as AdminImport } from './routes/_admin' import { Route as IndexImport } from './routes/index' import { Route as OcpIndexImport } from './routes/ocp/index' import { Route as OcpLotsImport } from './routes/ocp/lots' +import { Route as EomEomImport } from './routes/_eom/eom' import { Route as AuthProfileImport } from './routes/_auth/profile' import { Route as AdminSettingsImport } from './routes/_admin/settings' import { Route as AdminModulesImport } from './routes/_admin/modules' +import { Route as EomArticleAvImport } from './routes/_eom/article/$av' // Create/Update Routes @@ -36,6 +39,11 @@ const AboutRoute = AboutImport.update({ getParentRoute: () => rootRoute, } as any) +const EomRoute = EomImport.update({ + id: '/_eom', + getParentRoute: () => rootRoute, +} as any) + const AuthRoute = AuthImport.update({ id: '/_auth', getParentRoute: () => rootRoute, @@ -64,6 +72,12 @@ const OcpLotsRoute = OcpLotsImport.update({ getParentRoute: () => rootRoute, } as any) +const EomEomRoute = EomEomImport.update({ + id: '/eom', + path: '/eom', + getParentRoute: () => EomRoute, +} as any) + const AuthProfileRoute = AuthProfileImport.update({ id: '/profile', path: '/profile', @@ -82,6 +96,12 @@ const AdminModulesRoute = AdminModulesImport.update({ getParentRoute: () => AdminRoute, } as any) +const EomArticleAvRoute = EomArticleAvImport.update({ + id: '/article/$av', + path: '/article/$av', + getParentRoute: () => EomRoute, +} as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -107,6 +127,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthImport parentRoute: typeof rootRoute } + '/_eom': { + id: '/_eom' + path: '' + fullPath: '' + preLoaderRoute: typeof EomImport + parentRoute: typeof rootRoute + } '/about': { id: '/about' path: '/about' @@ -142,6 +169,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthProfileImport parentRoute: typeof AuthImport } + '/_eom/eom': { + id: '/_eom/eom' + path: '/eom' + fullPath: '/eom' + preLoaderRoute: typeof EomEomImport + parentRoute: typeof EomImport + } '/ocp/lots': { id: '/ocp/lots' path: '/ocp/lots' @@ -156,6 +190,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OcpIndexImport parentRoute: typeof rootRoute } + '/_eom/article/$av': { + id: '/_eom/article/$av' + path: '/article/$av' + fullPath: '/article/$av' + preLoaderRoute: typeof EomArticleAvImport + parentRoute: typeof EomImport + } } } @@ -183,28 +224,44 @@ const AuthRouteChildren: AuthRouteChildren = { const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) +interface EomRouteChildren { + EomEomRoute: typeof EomEomRoute + EomArticleAvRoute: typeof EomArticleAvRoute +} + +const EomRouteChildren: EomRouteChildren = { + EomEomRoute: EomEomRoute, + EomArticleAvRoute: EomArticleAvRoute, +} + +const EomRouteWithChildren = EomRoute._addFileChildren(EomRouteChildren) + export interface FileRoutesByFullPath { '/': typeof IndexRoute - '': typeof AuthRouteWithChildren + '': typeof EomRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute '/modules': typeof AdminModulesRoute '/settings': typeof AdminSettingsRoute '/profile': typeof AuthProfileRoute + '/eom': typeof EomEomRoute '/ocp/lots': typeof OcpLotsRoute '/ocp': typeof OcpIndexRoute + '/article/$av': typeof EomArticleAvRoute } export interface FileRoutesByTo { '/': typeof IndexRoute - '': typeof AuthRouteWithChildren + '': typeof EomRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute '/modules': typeof AdminModulesRoute '/settings': typeof AdminSettingsRoute '/profile': typeof AuthProfileRoute + '/eom': typeof EomEomRoute '/ocp/lots': typeof OcpLotsRoute '/ocp': typeof OcpIndexRoute + '/article/$av': typeof EomArticleAvRoute } export interface FileRoutesById { @@ -212,13 +269,16 @@ export interface FileRoutesById { '/': typeof IndexRoute '/_admin': typeof AdminRouteWithChildren '/_auth': typeof AuthRouteWithChildren + '/_eom': typeof EomRouteWithChildren '/about': typeof AboutRoute '/login': typeof LoginRoute '/_admin/modules': typeof AdminModulesRoute '/_admin/settings': typeof AdminSettingsRoute '/_auth/profile': typeof AuthProfileRoute + '/_eom/eom': typeof EomEomRoute '/ocp/lots': typeof OcpLotsRoute '/ocp/': typeof OcpIndexRoute + '/_eom/article/$av': typeof EomArticleAvRoute } export interface FileRouteTypes { @@ -231,8 +291,10 @@ export interface FileRouteTypes { | '/modules' | '/settings' | '/profile' + | '/eom' | '/ocp/lots' | '/ocp' + | '/article/$av' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -242,20 +304,25 @@ export interface FileRouteTypes { | '/modules' | '/settings' | '/profile' + | '/eom' | '/ocp/lots' | '/ocp' + | '/article/$av' id: | '__root__' | '/' | '/_admin' | '/_auth' + | '/_eom' | '/about' | '/login' | '/_admin/modules' | '/_admin/settings' | '/_auth/profile' + | '/_eom/eom' | '/ocp/lots' | '/ocp/' + | '/_eom/article/$av' fileRoutesById: FileRoutesById } @@ -263,6 +330,7 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute AdminRoute: typeof AdminRouteWithChildren AuthRoute: typeof AuthRouteWithChildren + EomRoute: typeof EomRouteWithChildren AboutRoute: typeof AboutRoute LoginRoute: typeof LoginRoute OcpLotsRoute: typeof OcpLotsRoute @@ -273,6 +341,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AdminRoute: AdminRouteWithChildren, AuthRoute: AuthRouteWithChildren, + EomRoute: EomRouteWithChildren, AboutRoute: AboutRoute, LoginRoute: LoginRoute, OcpLotsRoute: OcpLotsRoute, @@ -292,6 +361,7 @@ export const routeTree = rootRoute "/", "/_admin", "/_auth", + "/_eom", "/about", "/login", "/ocp/lots", @@ -314,6 +384,13 @@ export const routeTree = rootRoute "/_auth/profile" ] }, + "/_eom": { + "filePath": "_eom.tsx", + "children": [ + "/_eom/eom", + "/_eom/article/$av" + ] + }, "/about": { "filePath": "about.tsx" }, @@ -332,11 +409,19 @@ export const routeTree = rootRoute "filePath": "_auth/profile.tsx", "parent": "/_auth" }, + "/_eom/eom": { + "filePath": "_eom/eom.tsx", + "parent": "/_eom" + }, "/ocp/lots": { "filePath": "ocp/lots.tsx" }, "/ocp/": { "filePath": "ocp/index.tsx" + }, + "/_eom/article/$av": { + "filePath": "_eom/article/$av.tsx", + "parent": "/_eom" } } } diff --git a/frontend/src/routes/_eom.tsx b/frontend/src/routes/_eom.tsx new file mode 100644 index 0000000..38ce580 --- /dev/null +++ b/frontend/src/routes/_eom.tsx @@ -0,0 +1,13 @@ +import {createFileRoute, redirect} from "@tanstack/react-router"; + +export const Route = createFileRoute("/_eom")({ + //component: RouteComponent, + beforeLoad: async () => { + const auth = localStorage.getItem("auth_token"); + if (!auth) { + throw redirect({ + to: "/login", + }); + } + }, +}); diff --git a/frontend/src/routes/_eom/article/$av.tsx b/frontend/src/routes/_eom/article/$av.tsx new file mode 100644 index 0000000..3c65222 --- /dev/null +++ b/frontend/src/routes/_eom/article/$av.tsx @@ -0,0 +1,125 @@ +import {LstCard} from "@/components/extendedUI/LstCard"; +import {CardFooter, CardHeader} from "@/components/ui/card"; +import {Input} from "@/components/ui/input"; +import {Label} from "@radix-ui/react-dropdown-menu"; +import {createFileRoute} from "@tanstack/react-router"; + +export const Route = createFileRoute("/_eom/article/$av")({ + component: RouteComponent, + loader: () => { + return [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]; + }, +}); + +function RouteComponent() { + const {av} = Route.useParams(); + const loaded = Route.useLoaderData(); + + console.log(loaded, av); + return ( +
+
+
+

AV - Description

+
+
+ + +

Inventory Data

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + +

Purchasing

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +

Transfers

+
+
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + +

Calculations

+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
+ ); +} diff --git a/frontend/src/routes/_eom/eom.tsx b/frontend/src/routes/_eom/eom.tsx new file mode 100644 index 0000000..4ab49a8 --- /dev/null +++ b/frontend/src/routes/_eom/eom.tsx @@ -0,0 +1,14 @@ +import EomPage from "@/components/eom/EomPage"; +import {createFileRoute} from "@tanstack/react-router"; + +export const Route = createFileRoute("/_eom/eom")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+ +
+ ); +} diff --git a/server/services/eom/eomService.ts b/server/services/eom/eomService.ts new file mode 100644 index 0000000..b23f7d7 --- /dev/null +++ b/server/services/eom/eomService.ts @@ -0,0 +1,5 @@ +import {OpenAPIHono} from "@hono/zod-openapi"; + +const app = new OpenAPIHono(); + +export default app;