Ver código fonte

adding access right, daily mat forecast

master
pai
commit
fdad10a362
5 arquivos alterados com 121 adições e 73 exclusões
  1. +2
    -0
      src/app/api/scheduling/index.ts
  2. +7
    -0
      src/authorities.ts
  3. +0
    -6
      src/authorties.ts
  4. +41
    -41
      src/components/NavigationContent/NavigationContent.tsx
  5. +71
    -26
      src/config/authConfig.ts

+ 2
- 0
src/app/api/scheduling/index.ts Ver arquivo

@@ -9,6 +9,7 @@ export type ScheduleType = "all" | "rough" | "detailed" | "manual";
export interface RoughProdScheduleResult { export interface RoughProdScheduleResult {
id: number; id: number;
scheduleAt: number[]; scheduleAt: number[];
produceAt: number[];
schedulePeriod: number[]; schedulePeriod: number[];
schedulePeriodTo: number[]; schedulePeriodTo: number[];
totalEstProdCount: number; totalEstProdCount: number;
@@ -80,6 +81,7 @@ export interface RoughProdScheduleLineResultByBomByDate {
// Detailed // Detailed
export interface DetailedProdScheduleResult { export interface DetailedProdScheduleResult {
id: number; id: number;
produceAt: number[];
scheduleAt: number[]; scheduleAt: number[];
totalEstProdCount: number; totalEstProdCount: number;
totalFGType: number; totalFGType: number;


+ 7
- 0
src/authorities.ts Ver arquivo

@@ -0,0 +1,7 @@
export const [VIEW_USER, VIEW_DO, MAINTAIN_USER, VIEW_GROUP, MAINTAIN_GROUP] = [
"VIEW_USER",
"VIEW_DO",
"MAINTAIN_USER",
"VIEW_GROUP",
"MAINTAIN_GROUP",
];

+ 0
- 6
src/authorties.ts Ver arquivo

@@ -1,6 +0,0 @@
export const [VIEW_USER, MAINTAIN_USER, VIEW_GROUP, MAINTAIN_GROUP] = [
"VIEW_USER",
"MAINTAIN_USER",
"VIEW_GROUP",
"MAINTAIN_GROUP",
];

+ 41
- 41
src/components/NavigationContent/NavigationContent.tsx Ver arquivo

@@ -1,3 +1,4 @@
import { useSession } from "next-auth/react";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
@@ -24,6 +25,15 @@ import { usePathname } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
import Logo from "../Logo"; import Logo from "../Logo";
import {
VIEW_USER,
VIEW_DO,
MAINTAIN_USER,
VIEW_GROUP,
MAINTAIN_GROUP,
// Add more authorities as needed, e.g.:
// VIEW_PO, MAINTAIN_PO, VIEW_INVENTORY, etc.
} from "../../authorities";


interface NavigationItem { interface NavigationItem {
icon: React.ReactNode; icon: React.ReactNode;
@@ -31,9 +41,22 @@ interface NavigationItem {
path: string; path: string;
children?: NavigationItem[]; children?: NavigationItem[];
isHidden?: true | undefined; isHidden?: true | undefined;
requiredAbility?: string | string[];
} }


const NavigationContent: React.FC = () => { const NavigationContent: React.FC = () => {
const { data: session, status } = useSession();
const abilities = session?.user?.abilities ?? [];

// Helper: check if user has required permission
const hasAbility = (required?: string | string[]): boolean => {
if (!required) return true; // no requirement → always show
if (Array.isArray(required)) {
return required.some(ability => abilities.includes(ability));
}
return abilities.includes(required);
};

const navigationItems: NavigationItem[] = [ const navigationItems: NavigationItem[] = [
{ {
icon: <Dashboard />, icon: <Dashboard />,
@@ -108,49 +131,12 @@ const NavigationContent: React.FC = () => {
}, },
], ],
}, },
// {
// icon: <RequestQuote />,
// label: "Production",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "Job Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Job Order Traceablity ",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Work Order",
// path: "",
// },
// {
// icon: <RequestQuote />,
// label: "Work Order Traceablity ",
// path: "",
// },
// ],
// },
// {
// icon: <RequestQuote />,
// label: "Quality Control Log",
// path: "",
// children: [
// {
// icon: <RequestQuote />,
// label: "Quality Control Log",
// path: "",
// },
// ],
// },
{ {
icon: <RequestQuote />, icon: <RequestQuote />,
label: "Delivery", label: "Delivery",
path: "", path: "",
//requiredAbility: VIEW_DO,
requiredAbility: VIEW_USER,
children: [ children: [
{ {
icon: <RequestQuote />, icon: <RequestQuote />,
@@ -248,16 +234,19 @@ const NavigationContent: React.FC = () => {
icon: <RequestQuote />, icon: <RequestQuote />,
label: "Settings", label: "Settings",
path: "", path: "",
requiredAbility: [VIEW_USER, VIEW_GROUP],
children: [ children: [
{ {
icon: <RequestQuote />, icon: <RequestQuote />,
label: "User", label: "User",
path: "/settings/user", path: "/settings/user",
requiredAbility: VIEW_USER,
}, },
{ {
icon: <RequestQuote />, icon: <RequestQuote />,
label: "User Group", label: "User Group",
path: "/settings/user", path: "/settings/user",
requiredAbility: VIEW_GROUP,
}, },
// { // {
// icon: <RequestQuote />, // icon: <RequestQuote />,
@@ -360,7 +349,12 @@ const NavigationContent: React.FC = () => {
}; };


const renderNavigationItem = (item: NavigationItem) => { const renderNavigationItem = (item: NavigationItem) => {
if (!hasAbility(item.requiredAbility)) {
return null;
}

const isOpen = openItems.includes(item.label); const isOpen = openItems.includes(item.label);
const hasVisibleChildren = item.children?.some(child => hasAbility(child.requiredAbility));


return ( return (
<Box <Box
@@ -376,7 +370,7 @@ const NavigationContent: React.FC = () => {
<ListItemIcon>{item.icon}</ListItemIcon> <ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={t(item.label)} /> <ListItemText primary={t(item.label)} />
</ListItemButton> </ListItemButton>
{item.children && isOpen && (
{item.children && isOpen && hasVisibleChildren && (
<List sx={{ pl: 2 }}> <List sx={{ pl: 2 }}>
{item.children.map( {item.children.map(
(child) => !child.isHidden && renderNavigationItem(child), (child) => !child.isHidden && renderNavigationItem(child),
@@ -387,6 +381,10 @@ const NavigationContent: React.FC = () => {
); );
}; };


if (status === "loading") {
return <Box sx={{ width: NAVIGATION_CONTENT_WIDTH, p: 3 }}>Loading...</Box>;
}

return ( return (
<Box sx={{ width: NAVIGATION_CONTENT_WIDTH }}> <Box sx={{ width: NAVIGATION_CONTENT_WIDTH }}>
<Box sx={{ p: 3, display: "flex" }}> <Box sx={{ p: 3, display: "flex" }}>
@@ -397,7 +395,9 @@ const NavigationContent: React.FC = () => {
</Box> </Box>
<Divider /> <Divider />
<List component="nav"> <List component="nav">
{navigationItems.map((item) => renderNavigationItem(item))}
{navigationItems
.map(renderNavigationItem)
.filter(Boolean)}
{/* {navigationItems.map(({ icon, label, path }, index) => { {/* {navigationItems.map(({ icon, label, path }, index) => {
return ( return (
<Box <Box


+ 71
- 26
src/config/authConfig.ts Ver arquivo

@@ -1,12 +1,38 @@
import { AuthOptions, Session } from "next-auth";
// config/authConfig.ts (or wherever your authOptions live)
import { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials"; import CredentialsProvider from "next-auth/providers/credentials";
import { LOGIN_API_PATH } from "./api"; import { LOGIN_API_PATH } from "./api";


export interface SessionWithTokens extends Session {
accessToken: string | null;
refreshToken?: string;
abilities: string[];
id?: string | null;
// Extend the built-in types
declare module "next-auth" {
interface Session {
accessToken: string | null;
refreshToken?: string;
abilities: string[];
id?: string;
user: {
name?: string | null;
email?: string | null;
image?: string | null;
abilities: string[]; // add abilities to user object too if you need it client-side
};
}

interface User {
id?: string;
accessToken: string | null;
refreshToken?: string;
abilities: string[];
}
}

declare module "next-auth/jwt" {
interface JWT {
id?: string;
accessToken: string | null;
refreshToken?: string;
abilities: string[];
}
} }


export const authOptions: AuthOptions = { export const authOptions: AuthOptions = {
@@ -19,18 +45,26 @@ export const authOptions: AuthOptions = {
username: { label: "Username", type: "text" }, username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }, password: { label: "Password", type: "password" },
}, },
async authorize(credentials, req) {
async authorize(credentials) {
if (!credentials?.username || !credentials?.password) return null;

const res = await fetch(LOGIN_API_PATH, { const res = await fetch(LOGIN_API_PATH, {
method: "POST", method: "POST",
body: JSON.stringify(credentials), body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });


if (!res.ok) return null;

const user = await res.json(); const user = await res.json();


if (res.ok && user) {
return user;
// Important: next-auth expects the user object returned here
// to be serializable and contain the fields you want in token/session
// Ensure your backend returns: { id, accessToken, abilities, ...other fields }
if (user && user.abilities) {
return user; // this will be passed to jwt callback as `user`
} }

return null; return null;
}, },
}), }),
@@ -39,25 +73,36 @@ export const authOptions: AuthOptions = {
signIn: "/login", signIn: "/login",
}, },
callbacks: { callbacks: {
jwt(params) {
// Add the data from user to the token
const { token, user } = params;
const newToken = { ...token, ...user };
return newToken;
// Persist custom fields into the JWT token
async jwt({ token, user }) {
// First sign-in: `user` is available
if (user) {
token.id = user.id ?? token.sub; // fallback to sub if no id
token.accessToken = user.accessToken;
token.refreshToken = user.refreshToken;
token.abilities = user.abilities ?? [];
}

// On subsequent calls (token refresh, session access), user is not present
// so we just return the existing token with custom fields preserved
return token;
}, },
session({ session, token }) {
const sessionWithToken: SessionWithTokens = {
...session,
// Add the data from the token to the session
id: token.id as string | undefined,
accessToken: token.accessToken as string | null,
refreshToken: token.refreshToken as string | undefined,
abilities: token.abilities as string[],
};
if (sessionWithToken.user) {
sessionWithToken.user.abilities = token.abilities as string[];
// Expose custom fields to the client session
async session({ session, token }) {
session.id = token.id as string | undefined;
session.accessToken = token.accessToken as string | null;
session.refreshToken = token.refreshToken as string | undefined;
session.abilities = token.abilities as string[];
// Also add abilities to session.user for easier client-side access
if (session.user) {
session.user.abilities = token.abilities as string[];
} }
return sessionWithToken;

return session;
}, },
}, },
}; };

export default authOptions;

Carregando…
Cancelar
Salvar