瀏覽代碼

update master data authorities

tags/Baseline_30082024_FRONTEND_UAT
cyril.tsui 1 年之前
父節點
當前提交
a2afe91405
共有 30 個檔案被更改,包括 399 行新增107 行删除
  1. +7
    -2
      src/app/(main)/settings/company/page.tsx
  2. +6
    -2
      src/app/(main)/settings/customer/page.tsx
  3. +7
    -2
      src/app/(main)/settings/department/page.tsx
  4. +7
    -2
      src/app/(main)/settings/position/page.tsx
  5. +8
    -2
      src/app/(main)/settings/skill/page.tsx
  6. +8
    -2
      src/app/(main)/settings/staff/page.tsx
  7. +6
    -2
      src/app/(main)/settings/subsidiary/page.tsx
  8. +8
    -2
      src/app/(main)/settings/team/page.tsx
  9. +5
    -2
      src/components/CompanyHoliday/CompanyHoliday.tsx
  10. +3
    -2
      src/components/CompanyHoliday/CompanyHolidayWrapper.tsx
  11. +8
    -2
      src/components/CompanySearch/CompanySearch.tsx
  12. +3
    -2
      src/components/CompanySearch/CompanySearchWrapper.tsx
  13. +7
    -2
      src/components/CustomerSearch/CustomerSearch.tsx
  14. +3
    -2
      src/components/CustomerSearch/CustomerSearchWrapper.tsx
  15. +7
    -2
      src/components/DepartmentSearch/DepartmentSearch.tsx
  16. +3
    -2
      src/components/DepartmentSearch/DepartmentSearchWrapper.tsx
  17. +98
    -13
      src/components/NavigationContent/NavigationContent.tsx
  18. +7
    -2
      src/components/PositionSearch/PositionSearch.tsx
  19. +3
    -2
      src/components/PositionSearch/PositionSearchWrapper.tsx
  20. +6
    -3
      src/components/SalarySearch/SalarySearch.tsx
  21. +3
    -2
      src/components/SalarySearch/SalarySearchWrapper.tsx
  22. +7
    -1
      src/components/SkillSearch/SkillSearch.tsx
  23. +3
    -2
      src/components/SkillSearch/SkillSearchWrapper.tsx
  24. +9
    -4
      src/components/StaffSearch/StaffSearch.tsx
  25. +3
    -3
      src/components/StaffSearch/StaffSearchWrapper.tsx
  26. +7
    -2
      src/components/SubsidiarySearch/SubsidiarySearch.tsx
  27. +3
    -2
      src/components/SubsidiarySearch/SubsidiarySearchWrapper.tsx
  28. +8
    -2
      src/components/TeamSearch/TeamSearch.tsx
  29. +3
    -2
      src/components/TeamSearch/TeamSearchWrapper.tsx
  30. +143
    -35
      src/middleware.ts

+ 7
- 2
src/app/(main)/settings/company/page.tsx 查看文件

@@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography";
import Link from "next/link"; import Link from "next/link";
import { Suspense } from "react"; import { Suspense } from "react";
import { fetchCompanys, preloadCompanys } from "@/app/api/companys"; import { fetchCompanys, preloadCompanys } from "@/app/api/companys";
import { getUserAbilities } from "@/app/utils/commonUtil";
import { MAINTAIN_COMPANY } from "@/middleware";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Company", title: "Company",
@@ -20,6 +22,9 @@ const Company: React.FC = async () => {
fetchCompanys(); fetchCompanys();
preloadCompanys(); preloadCompanys();
const abilities = await getUserAbilities()
const maintainCompany = abilities.includes(MAINTAIN_COMPANY)

return ( return (
<> <>
<Stack <Stack
@@ -31,14 +36,14 @@ const Company: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Company")} {t("Company")}
</Typography> </Typography>
<Button
{maintainCompany && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/company/create" href="/settings/company/create"
> >
{t("Create Company")} {t("Create Company")}
</Button>
</Button>}
</Stack> </Stack>
<Suspense fallback={<CompanySearch.Loading />}> <Suspense fallback={<CompanySearch.Loading />}>
<CompanySearch/> <CompanySearch/>


+ 6
- 2
src/app/(main)/settings/customer/page.tsx 查看文件

@@ -9,6 +9,8 @@ import { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import { Suspense } from "react"; import { Suspense } from "react";
import { I18nProvider } from "@/i18n"; import { I18nProvider } from "@/i18n";
import { getUserAbilities } from "@/app/utils/commonUtil";
import { MAINTAIN_CLIENT } from "@/middleware";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Customer", title: "Customer",
@@ -17,6 +19,8 @@ export const metadata: Metadata = {
const Customer: React.FC = async () => { const Customer: React.FC = async () => {
const { t } = await getServerI18n("customer"); const { t } = await getServerI18n("customer");
preloadAllCustomers(); preloadAllCustomers();
const abilities = await getUserAbilities()
const maintainClient = abilities.includes(MAINTAIN_CLIENT)


return ( return (
<> <>
@@ -29,14 +33,14 @@ const Customer: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Customer")} {t("Customer")}
</Typography> </Typography>
<Button
{maintainClient && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/customer/create" href="/settings/customer/create"
> >
{t("Create Customer")} {t("Create Customer")}
</Button>
</Button>}
</Stack> </Stack>
<I18nProvider namespaces={["customer", "common"]}> <I18nProvider namespaces={["customer", "common"]}>
<Suspense fallback={<CustomerSearch.Loading />}> <Suspense fallback={<CustomerSearch.Loading />}>


+ 7
- 2
src/app/(main)/settings/department/page.tsx 查看文件

@@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography";
import Link from "next/link"; import Link from "next/link";
import { Suspense } from "react"; import { Suspense } from "react";
import { fetchDepartments, preloadDepartments } from "@/app/api/departments"; import { fetchDepartments, preloadDepartments } from "@/app/api/departments";
import { getUserAbilities } from "@/app/utils/commonUtil";
import { MAINTAIN_DEPARTMENT } from "@/middleware";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Department", title: "Department",
@@ -20,6 +22,9 @@ const Department: React.FC = async () => {
// fetchDepartments(); // fetchDepartments();
// preloadDepartments(); // preloadDepartments();
const abilities = await getUserAbilities()
const maintainDepartment = abilities.includes(MAINTAIN_DEPARTMENT)

return ( return (
<> <>
<Stack <Stack
@@ -31,14 +36,14 @@ const Department: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Department")} {t("Department")}
</Typography> </Typography>
<Button
{maintainDepartment && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/department/new" href="/settings/department/new"
> >
{t("Create Department")} {t("Create Department")}
</Button>
</Button>}
</Stack> </Stack>
<Suspense fallback={<DepartmentSearch.Loading />}> <Suspense fallback={<DepartmentSearch.Loading />}>
<DepartmentSearch/> <DepartmentSearch/>


+ 7
- 2
src/app/(main)/settings/position/page.tsx 查看文件

@@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography";
import Link from "next/link"; import Link from "next/link";
import { Suspense } from "react"; import { Suspense } from "react";
import { fetchPositions, preloadPositions } from "@/app/api/positions"; import { fetchPositions, preloadPositions } from "@/app/api/positions";
import { getUserAbilities } from "@/app/utils/commonUtil";
import { MAINTAIN_POSITION } from "@/middleware";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Position", title: "Position",
@@ -20,6 +22,9 @@ const Position: React.FC = async () => {
// fetchPositions(); // fetchPositions();
// preloadPositions(); // preloadPositions();
const abilities = await getUserAbilities()
const maintainPosition = abilities.includes(MAINTAIN_POSITION)

return ( return (
<> <>
<Stack <Stack
@@ -31,14 +36,14 @@ const Position: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Position")} {t("Position")}
</Typography> </Typography>
<Button
{maintainPosition && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/position/new" href="/settings/position/new"
> >
{t("Create Position")} {t("Create Position")}
</Button>
</Button>}
</Stack> </Stack>
<Suspense fallback={<PositionSearch.Loading />}> <Suspense fallback={<PositionSearch.Loading />}>
<PositionSearch/> <PositionSearch/>


+ 8
- 2
src/app/(main)/settings/skill/page.tsx 查看文件

@@ -1,7 +1,9 @@
import { preloadClaims } from "@/app/api/claims"; import { preloadClaims } from "@/app/api/claims";
import { getUserAbilities } from "@/app/utils/commonUtil";
// import { preloadSkill, preloadTeamLeads } from "@/app/api/staff"; // import { preloadSkill, preloadTeamLeads } from "@/app/api/staff";
import SkillSearch from "@/components/SkillSearch"; import SkillSearch from "@/components/SkillSearch";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import { MAINTAIN_SKILL } from "@/middleware";
import Add from "@mui/icons-material/Add"; import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
@@ -18,6 +20,10 @@ const Skill: React.FC = async () => {
const { t } = await getServerI18n("skill"); const { t } = await getServerI18n("skill");
// preloadTeamLeads(); // preloadTeamLeads();
// preloadSkill(); // preloadSkill();

const abilities = await getUserAbilities()
const maintainSkill = abilities.includes(MAINTAIN_SKILL)
return ( return (
<> <>
<Stack <Stack
@@ -29,14 +35,14 @@ const Skill: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Skill")} {t("Skill")}
</Typography> </Typography>
<Button
{maintainSkill && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/skill/create" href="/settings/skill/create"
> >
{t("Create Skill")} {t("Create Skill")}
</Button>
</Button>}
</Stack> </Stack>
<I18nProvider namespaces={["skill", "common"]}> <I18nProvider namespaces={["skill", "common"]}>
<Suspense fallback={<SkillSearch.Loading />}> <Suspense fallback={<SkillSearch.Loading />}>


+ 8
- 2
src/app/(main)/settings/staff/page.tsx 查看文件

@@ -1,7 +1,9 @@
import { preloadClaims } from "@/app/api/claims"; import { preloadClaims } from "@/app/api/claims";
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
import { getUserAbilities } from "@/app/utils/commonUtil";
import StaffSearch from "@/components/StaffSearch"; import StaffSearch from "@/components/StaffSearch";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import { MAINTAIN_STAFF } from "@/middleware";
import Add from "@mui/icons-material/Add"; import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
@@ -18,6 +20,10 @@ const Staff: React.FC = async () => {
const { t } = await getServerI18n("staff"); const { t } = await getServerI18n("staff");
preloadTeamLeads(); preloadTeamLeads();
preloadStaff(); preloadStaff();

const abilities = await getUserAbilities();
const maintainStaff = abilities.includes(MAINTAIN_STAFF)

return ( return (
<> <>
<Stack <Stack
@@ -29,14 +35,14 @@ const Staff: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Staff")} {t("Staff")}
</Typography> </Typography>
<Button
{maintainStaff && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/staff/create" href="/settings/staff/create"
> >
{t("Create Staff")} {t("Create Staff")}
</Button>
</Button>}
</Stack> </Stack>
<I18nProvider namespaces={["staff", "common"]}> <I18nProvider namespaces={["staff", "common"]}>
<Suspense fallback={<StaffSearch.Loading />}> <Suspense fallback={<StaffSearch.Loading />}>


+ 6
- 2
src/app/(main)/settings/subsidiary/page.tsx 查看文件

@@ -9,6 +9,8 @@ import { Suspense } from "react";
import { I18nProvider } from "@/i18n"; import { I18nProvider } from "@/i18n";
import { preloadAllSubsidiaries } from "@/app/api/subsidiary"; import { preloadAllSubsidiaries } from "@/app/api/subsidiary";
import SubsidiarySearch from "@/components/SubsidiarySearch"; import SubsidiarySearch from "@/components/SubsidiarySearch";
import { getUserAbilities } from "@/app/utils/commonUtil";
import { MAINTAIN_SUBSIDIARY } from "@/middleware";


export const metadata: Metadata = { export const metadata: Metadata = {
title: "Subsidiary", title: "Subsidiary",
@@ -17,6 +19,8 @@ export const metadata: Metadata = {
const Subsidiary: React.FC = async () => { const Subsidiary: React.FC = async () => {
const { t } = await getServerI18n("subsidiary"); const { t } = await getServerI18n("subsidiary");
preloadAllSubsidiaries(); preloadAllSubsidiaries();
const abilities = await getUserAbilities()
const maintainSubsidiary = abilities.includes(MAINTAIN_SUBSIDIARY)


return ( return (
<> <>
@@ -29,14 +33,14 @@ const Subsidiary: React.FC = async () => {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Subsidiary")} {t("Subsidiary")}
</Typography> </Typography>
<Button
{maintainSubsidiary && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/subsidiary/create" href="/settings/subsidiary/create"
> >
{t("Create Subsidiary")} {t("Create Subsidiary")}
</Button>
</Button>}
</Stack> </Stack>
<I18nProvider namespaces={["subsidiary", "common"]}> <I18nProvider namespaces={["subsidiary", "common"]}>
<Suspense fallback={<SubsidiarySearch.Loading />}> <Suspense fallback={<SubsidiarySearch.Loading />}>


+ 8
- 2
src/app/(main)/settings/team/page.tsx 查看文件

@@ -1,8 +1,10 @@
import { preloadClaims } from "@/app/api/claims"; import { preloadClaims } from "@/app/api/claims";
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
import { getUserAbilities } from "@/app/utils/commonUtil";
import StaffSearch from "@/components/StaffSearch"; import StaffSearch from "@/components/StaffSearch";
import TeamSearch from "@/components/TeamSearch"; import TeamSearch from "@/components/TeamSearch";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import { MAINTAIN_TEAM } from "@/middleware";
import Add from "@mui/icons-material/Add"; import Add from "@mui/icons-material/Add";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
@@ -21,6 +23,10 @@ export const metadata: Metadata = {
const { t } = await getServerI18n("team"); const { t } = await getServerI18n("team");
// preloadTeamLeads(); // preloadTeamLeads();
// preloadStaff(); // preloadStaff();

const abilities = await getUserAbilities()
const maintainTeam = abilities.includes(MAINTAIN_TEAM)

return ( return (
<> <>
<Stack <Stack
@@ -32,14 +38,14 @@ export const metadata: Metadata = {
<Typography variant="h4" marginInlineEnd={2}> <Typography variant="h4" marginInlineEnd={2}>
{t("Team")} {t("Team")}
</Typography> </Typography>
<Button
{maintainTeam && <Button
variant="contained" variant="contained"
startIcon={<Add />} startIcon={<Add />}
LinkComponent={Link} LinkComponent={Link}
href="/settings/team/create" href="/settings/team/create"
> >
{t("Create Team")} {t("Create Team")}
</Button>
</Button>}
</Stack> </Stack>
<I18nProvider namespaces={["team", "common"]}> <I18nProvider namespaces={["team", "common"]}>
<Suspense fallback={<TeamSearch.Loading />}> <Suspense fallback={<TeamSearch.Loading />}>


+ 5
- 2
src/components/CompanyHoliday/CompanyHoliday.tsx 查看文件

@@ -23,16 +23,19 @@ import {
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { deleteDialog, submitDialog } from "../Swal/CustomAlerts"; import { deleteDialog, submitDialog } from "../Swal/CustomAlerts";
import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils";
import { MAINTAIN_HOLIDAY } from "@/middleware";


interface Props { interface Props {
holidays: HolidaysList[]; holidays: HolidaysList[];
abilities: String[];
} }


const CompanyHoliday: React.FC<Props> = ({ holidays }) => {
const CompanyHoliday: React.FC<Props> = ({ holidays, abilities }) => {
const { t } = useTranslation("holidays"); const { t } = useTranslation("holidays");
const router = useRouter(); const router = useRouter();
const formValues = useFormContext(); const formValues = useFormContext();
const [serverError, setServerError] = useState(""); const [serverError, setServerError] = useState("");
const maintainHoliday = abilities.includes(MAINTAIN_HOLIDAY)


const companyHolidays = useMemo( const companyHolidays = useMemo(
() => [...getPublicHolidaysForNYears(2), ...holidays], () => [...getPublicHolidaysForNYears(2), ...holidays],
@@ -139,7 +142,7 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => {
}} }}
/> />
<CompanyHolidayDialog <CompanyHolidayDialog
open={open}
open={maintainHoliday && open}
onClose={handleClose} onClose={handleClose}
title={ title={
!editable !editable


+ 3
- 2
src/components/CompanyHoliday/CompanyHolidayWrapper.tsx 查看文件

@@ -6,6 +6,7 @@ import { fetchCompanys } from "@/app/api/companys";
import Holidays from "date-holidays"; import Holidays from "date-holidays";
import { HolidaysResult, fetchHolidays, HolidaysList } from "@/app/api/holidays"; import { HolidaysResult, fetchHolidays, HolidaysList } from "@/app/api/holidays";
import { convertDateArrayToString } from "@/app/utils/formatUtil"; import { convertDateArrayToString } from "@/app/utils/formatUtil";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof CompanyHolidayLoading; Loading: typeof CompanyHolidayLoading;
@@ -14,7 +15,7 @@ interface SubComponents {
const CompanyHolidayWrapper: React.FC & SubComponents = async () => { const CompanyHolidayWrapper: React.FC & SubComponents = async () => {
// const Companys = await fetchCompanys(); // const Companys = await fetchCompanys();


const companyHolidays: HolidaysResult[] = await fetchHolidays()
const [companyHolidays, abilities] = await Promise.all([fetchHolidays(), getUserAbilities()])


// console.log(companyHolidays) // console.log(companyHolidays)
const convertedHolidays = companyHolidays.map((holiday) => { const convertedHolidays = companyHolidays.map((holiday) => {
@@ -26,7 +27,7 @@ const CompanyHolidayWrapper: React.FC & SubComponents = async () => {
}) })


return <CompanyHoliday holidays={convertedHolidays as HolidaysList[]} />;
return <CompanyHoliday holidays={convertedHolidays as HolidaysList[]} abilities={abilities}/>;
}; };


CompanyHolidayWrapper.Loading = CompanyHolidayLoading; CompanyHolidayWrapper.Loading = CompanyHolidayLoading;


+ 8
- 2
src/components/CompanySearch/CompanySearch.tsx 查看文件

@@ -10,21 +10,25 @@ import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deleteCompany } from "@/app/api/companys/actions"; import { deleteCompany } from "@/app/api/companys/actions";
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import { MAINTAIN_COMPANY } from "@/middleware";


interface Props { interface Props {
companys: CompanyResult[]; companys: CompanyResult[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<CompanyResult, "id">>; type SearchQuery = Partial<Omit<CompanyResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const CompanySearch: React.FC<Props> = ({ companys }) => {
const CompanySearch: React.FC<Props> = ({ companys, abilities }) => {
const { t } = useTranslation("companys"); const { t } = useTranslation("companys");


const router = useRouter() const router = useRouter()


const [filteredCompanys, setFilteredCompanys] = useState(companys); const [filteredCompanys, setFilteredCompanys] = useState(companys);


const maintainCompany = abilities.includes(MAINTAIN_COMPANY)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ label: t("Company code"), paramName: "companyCode", type: "text" }, { label: t("Company code"), paramName: "companyCode", type: "text" },
@@ -62,6 +66,7 @@ const CompanySearch: React.FC<Props> = ({ companys }) => {
label: t("Details"), label: t("Details"),
onClick: onProjectClick, onClick: onProjectClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainCompany,
}, },
{ name: "companyCode", label: t("Company Code") }, { name: "companyCode", label: t("Company Code") },
{ name: "name", label: t("Company Name") }, { name: "name", label: t("Company Name") },
@@ -74,7 +79,8 @@ const CompanySearch: React.FC<Props> = ({ companys }) => {
label: t("Delete"), label: t("Delete"),
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainCompany,
}, },
], ],
[t, onProjectClick], [t, onProjectClick],


+ 3
- 2
src/components/CompanySearch/CompanySearchWrapper.tsx 查看文件

@@ -3,6 +3,7 @@ import React from "react";
import CompanySearch from "./CompanySearch"; import CompanySearch from "./CompanySearch";
import CompanySearchLoading from "./CompanySearchLoading"; import CompanySearchLoading from "./CompanySearchLoading";
import { fetchCompanys } from "@/app/api/companys"; import { fetchCompanys } from "@/app/api/companys";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof CompanySearchLoading; Loading: typeof CompanySearchLoading;
@@ -10,9 +11,9 @@ interface SubComponents {


const CompanySearchWrapper: React.FC & SubComponents = async () => { const CompanySearchWrapper: React.FC & SubComponents = async () => {
// const Companys = await fetchCompanys(); // const Companys = await fetchCompanys();
const Companys = await fetchCompanys();
const [Companys, abilities] = await Promise.all([fetchCompanys(), getUserAbilities()]);


return <CompanySearch companys={Companys} />;
return <CompanySearch companys={Companys} abilities={abilities}/>;
}; };


CompanySearchWrapper.Loading = CompanySearchLoading; CompanySearchWrapper.Loading = CompanySearchLoading;


+ 7
- 2
src/components/CustomerSearch/CustomerSearch.tsx 查看文件

@@ -10,18 +10,21 @@ import DeleteIcon from '@mui/icons-material/Delete';
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deleteCustomer } from "@/app/api/customer/actions"; import { deleteCustomer } from "@/app/api/customer/actions";
import { MAINTAIN_CLIENT } from "@/middleware";


interface Props { interface Props {
customers: Customer[]; customers: Customer[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<Customer, "id">>; type SearchQuery = Partial<Omit<Customer, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const CustomerSearch: React.FC<Props> = ({ customers }) => {
const CustomerSearch: React.FC<Props> = ({ customers, abilities }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const maintainClient = abilities.includes(MAINTAIN_CLIENT)


const [filteredCustomers, setFilteredCustomers] = useState(customers); const [filteredCustomers, setFilteredCustomers] = useState(customers);
useEffect(() => { useEffect(() => {
@@ -60,6 +63,7 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => {
label: t("Details"), label: t("Details"),
onClick: onTaskClick, onClick: onTaskClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainClient
}, },
{ name: "code", label: t("Customer Code") }, { name: "code", label: t("Customer Code") },
{ name: "name", label: t("Customer Name") }, { name: "name", label: t("Customer Name") },
@@ -68,7 +72,8 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => {
label: t("Delete"), label: t("Delete"),
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainClient
}, },
], ],
[onTaskClick, t], [onTaskClick, t],


+ 3
- 2
src/components/CustomerSearch/CustomerSearchWrapper.tsx 查看文件

@@ -2,15 +2,16 @@ import { fetchAllCustomers } from "@/app/api/customer";
import React from "react"; import React from "react";
import CustomerSearch from "./CustomerSearch"; import CustomerSearch from "./CustomerSearch";
import CustomerSearchLoading from "./CustomerSearchLoading"; import CustomerSearchLoading from "./CustomerSearchLoading";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof CustomerSearchLoading; Loading: typeof CustomerSearchLoading;
} }


const CustomerSearchWrapper: React.FC & SubComponents = async () => { const CustomerSearchWrapper: React.FC & SubComponents = async () => {
const [customers] = await Promise.all([fetchAllCustomers()]);
const [customers, abilities] = await Promise.all([fetchAllCustomers(), getUserAbilities()]);


return <CustomerSearch customers={customers} />;
return <CustomerSearch customers={customers} abilities={abilities}/>;
}; };


CustomerSearchWrapper.Loading = CustomerSearchLoading; CustomerSearchWrapper.Loading = CustomerSearchLoading;


+ 7
- 2
src/components/DepartmentSearch/DepartmentSearch.tsx 查看文件

@@ -10,19 +10,22 @@ import { useRouter } from "next/navigation";
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deleteDepartment } from "@/app/api/departments/actions"; import { deleteDepartment } from "@/app/api/departments/actions";
import { MAINTAIN_DEPARTMENT } from "@/middleware";


interface Props { interface Props {
departments: DepartmentResult[]; departments: DepartmentResult[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<DepartmentResult, "id">>; type SearchQuery = Partial<Omit<DepartmentResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const DepartmentSearch: React.FC<Props> = ({ departments }) => {
const DepartmentSearch: React.FC<Props> = ({ departments, abilities }) => {
const { t } = useTranslation("departments"); const { t } = useTranslation("departments");
const router = useRouter(); const router = useRouter();


const [filteredDepartments, setFilteredDepartments] = useState(departments); const [filteredDepartments, setFilteredDepartments] = useState(departments);
const maintainDepartment = abilities.includes(MAINTAIN_DEPARTMENT)


const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
@@ -60,6 +63,7 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => {
label: t("Details"), label: t("Details"),
onClick: onProjectClick, onClick: onProjectClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainDepartment,
}, },
{ name: "code", label: t("Department Code") }, { name: "code", label: t("Department Code") },
{ name: "name", label: t("Department Name") }, { name: "name", label: t("Department Name") },
@@ -69,7 +73,8 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => {
label: t("Delete"), label: t("Delete"),
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainDepartment,
}, },
], ],
[t, onProjectClick], [t, onProjectClick],


+ 3
- 2
src/components/DepartmentSearch/DepartmentSearchWrapper.tsx 查看文件

@@ -3,16 +3,17 @@ import React from "react";
import DepartmentSearch from "./DepartmentSearch"; import DepartmentSearch from "./DepartmentSearch";
import DepartmentSearchLoading from "./DepartmentSearchLoading"; import DepartmentSearchLoading from "./DepartmentSearchLoading";
import { fetchDepartments } from "@/app/api/departments"; import { fetchDepartments } from "@/app/api/departments";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof DepartmentSearchLoading; Loading: typeof DepartmentSearchLoading;
} }


const DepartmentSearchWrapper: React.FC & SubComponents = async () => { const DepartmentSearchWrapper: React.FC & SubComponents = async () => {
const Departments = await fetchDepartments();
const [Departments, abilities] = await Promise.all([fetchDepartments(), getUserAbilities()]);
// const Departments:any[] = [] // const Departments:any[] = []


return <DepartmentSearch departments={Departments} />;
return <DepartmentSearch departments={Departments} abilities={abilities} />;
}; };


DepartmentSearchWrapper.Loading = DepartmentSearchLoading; DepartmentSearchWrapper.Loading = DepartmentSearchLoading;


+ 98
- 13
src/components/NavigationContent/NavigationContent.tsx 查看文件

@@ -39,15 +39,34 @@ import {
GENERATE_REPORTS, GENERATE_REPORTS,
IMPORT_INVOICE, IMPORT_INVOICE,
IMPORT_RECEIPT, IMPORT_RECEIPT,
MAINTAIN_MASTERDATA,
MAINTAIN_PROJECT, MAINTAIN_PROJECT,
MAINTAIN_TASK_TEMPLATE, MAINTAIN_TASK_TEMPLATE,
MAINTAIN_USER, MAINTAIN_USER,
VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_ALL,
VIEW_DASHBOARD_SELF, VIEW_DASHBOARD_SELF,
VIEW_MASTERDATA,
VIEW_PROJECT, VIEW_PROJECT,
VIEW_USER,
VIEW_CLIENT,
VIEW_SUBSIDIARY,
VIEW_STAFF,
VIEW_COMPANY,
VIEW_SKILL,
VIEW_DEPARTMENT,
VIEW_POSITION,
VIEW_SALARY,
VIEW_TEAM,
VIEW_USER_GROUP,
VIEW_HOLIDAY,
MAINTAIN_CLIENT,
MAINTAIN_SUBSIDIARY,
MAINTAIN_STAFF,
MAINTAIN_COMPANY,
MAINTAIN_SKILL,
MAINTAIN_DEPARTMENT,
MAINTAIN_POSITION,
MAINTAIN_SALARY,
MAINTAIN_TEAM,
MAINTAIN_USER_GROUP,
MAINTAIN_HOLIDAY,
} from "@/middleware"; } from "@/middleware";
import { SessionWithAbilities } from "../AppBar/NavigationToggle"; import { SessionWithAbilities } from "../AppBar/NavigationToggle";
import { authOptions } from "@/config/authConfig"; import { authOptions } from "@/config/authConfig";
@@ -217,35 +236,101 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => {
icon: <Settings />, icon: <Settings />,
label: "Setting", label: "Setting",
path: "", path: "",
isHidden: ![VIEW_MASTERDATA, MAINTAIN_MASTERDATA].some((ability) =>
isHidden: ![
VIEW_CLIENT,
VIEW_SUBSIDIARY,
VIEW_STAFF,
VIEW_COMPANY,
VIEW_SKILL,
VIEW_DEPARTMENT,
VIEW_POSITION,
VIEW_SALARY,
VIEW_TEAM,
VIEW_USER_GROUP,
VIEW_HOLIDAY,
MAINTAIN_CLIENT,
MAINTAIN_SUBSIDIARY,
MAINTAIN_STAFF,
MAINTAIN_COMPANY,
MAINTAIN_SKILL,
MAINTAIN_DEPARTMENT,
MAINTAIN_POSITION,
MAINTAIN_SALARY,
MAINTAIN_TEAM,
MAINTAIN_USER_GROUP,
MAINTAIN_HOLIDAY
].some((ability) =>
abilities!.includes(ability), abilities!.includes(ability),
), ),
children: [ children: [
{ icon: <GroupIcon />, label: "Client", path: "/settings/customer" },
{
icon: <GroupIcon />,
label: "Client",
path: "/settings/customer",
isHidden: ![VIEW_CLIENT, MAINTAIN_CLIENT].some((ability) => abilities!.includes(ability),),
},
{ {
icon: <BusinessIcon />, icon: <BusinessIcon />,
label: "Subsidiary", label: "Subsidiary",
path: "/settings/subsidiary", path: "/settings/subsidiary",
isHidden: ![VIEW_SUBSIDIARY, MAINTAIN_SUBSIDIARY].some((ability) => abilities!.includes(ability),),
},
{
icon: <Staff />,
label: "Staff",
path: "/settings/staff",
isHidden: ![VIEW_STAFF, MAINTAIN_STAFF].some((ability) => abilities!.includes(ability),),
},
{
icon: <Company />,
label: "Company",
path: "/settings/company",
isHidden: ![VIEW_COMPANY, MAINTAIN_COMPANY].some((ability) => abilities!.includes(ability),),
},
{
icon: <EmojiEventsIcon />,
label: "Skill",
path: "/settings/skill",
isHidden: ![VIEW_SKILL, MAINTAIN_SKILL].some((ability) => abilities!.includes(ability),),
}, },
{ icon: <Staff />, label: "Staff", path: "/settings/staff" },
{ icon: <Company />, label: "Company", path: "/settings/company" },
{ icon: <EmojiEventsIcon />, label: "Skill", path: "/settings/skill" },
{ {
icon: <Department />, icon: <Department />,
label: "Department", label: "Department",
path: "/settings/department", path: "/settings/department",
isHidden: ![VIEW_DEPARTMENT, MAINTAIN_DEPARTMENT].some((ability) => abilities!.includes(ability),),
},
{
icon: <Position />,
label: "Position",
path: "/settings/position",
isHidden: ![VIEW_POSITION, MAINTAIN_POSITION].some((ability) => abilities!.includes(ability),),
},
{
icon: <Salary />,
label: "Salary",
path: "/settings/salary",
isHidden: ![VIEW_SALARY, MAINTAIN_SALARY].some((ability) => abilities!.includes(ability),),
},
{
icon: <Team />,
label: "Team",
path: "/settings/team",
isHidden: ![VIEW_TEAM, MAINTAIN_TEAM].some((ability) => abilities!.includes(ability),),
}, },
{ icon: <Position />, label: "Position", path: "/settings/position" },
{ icon: <Salary />, label: "Salary", path: "/settings/salary" },
{ icon: <Team />, label: "Team", path: "/settings/team" },
// { icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" }, // { icon: <ManageAccountsIcon />, label: "User", path: "/settings/user" },
{ {
icon: <ManageAccountsIcon />, icon: <ManageAccountsIcon />,
label: "User Group", label: "User Group",
path: "/settings/group", path: "/settings/group",
isHidden: ![VIEW_USER_GROUP, MAINTAIN_USER_GROUP].some((ability) => abilities!.includes(ability),),
},
{
icon: <Holiday />,
label: "Holiday",
path: "/settings/holiday",
isHidden: ![VIEW_HOLIDAY, MAINTAIN_HOLIDAY].some((ability) => abilities!.includes(ability),),
}, },
{ icon: <Holiday />, label: "Holiday", path: "/settings/holiday"},
{ icon: <FileUploadIcon />, label: "Import Excel File", path: "/settings/import", isHidden: username !== "2fi"},
{ icon: <FileUploadIcon />, label: "Import Excel File", path: "/settings/import", isHidden: username !== "2fi" },
], ],
}, },
]; ];


+ 7
- 2
src/components/PositionSearch/PositionSearch.tsx 查看文件

@@ -10,19 +10,22 @@ import { useRouter } from "next/navigation";
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { deletePosition } from "@/app/api/positions/actions"; import { deletePosition } from "@/app/api/positions/actions";
import { MAINTAIN_POSITION } from "@/middleware";


interface Props { interface Props {
positions: PositionResult[]; positions: PositionResult[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<PositionResult, "id">>; type SearchQuery = Partial<Omit<PositionResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const PositionSearch: React.FC<Props> = ({ positions }) => {
const PositionSearch: React.FC<Props> = ({ positions, abilities }) => {
const { t } = useTranslation("positions"); const { t } = useTranslation("positions");
const router = useRouter(); const router = useRouter();


const [filteredPositions, setFilteredPositions] = useState(positions); const [filteredPositions, setFilteredPositions] = useState(positions);
const maintainPosition = abilities.includes(MAINTAIN_POSITION)


const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
@@ -61,6 +64,7 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
label: t("Details"), label: t("Details"),
onClick: onPositionClick, onClick: onPositionClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainPosition,
}, },
{ name: "code", label: t("Position Code") }, { name: "code", label: t("Position Code") },
{ name: "name", label: t("Position Name") }, { name: "name", label: t("Position Name") },
@@ -70,7 +74,8 @@ const PositionSearch: React.FC<Props> = ({ positions }) => {
label: t("Delete"), label: t("Delete"),
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainPosition,
}, },
], ],
[t, onPositionClick], [t, onPositionClick],


+ 3
- 2
src/components/PositionSearch/PositionSearchWrapper.tsx 查看文件

@@ -3,16 +3,17 @@ import React from "react";
import PositionSearch from "./PositionSearch"; import PositionSearch from "./PositionSearch";
import PositionSearchLoading from "./PositionSearchLoading"; import PositionSearchLoading from "./PositionSearchLoading";
import { fetchPositions } from "@/app/api/positions"; import { fetchPositions } from "@/app/api/positions";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof PositionSearchLoading; Loading: typeof PositionSearchLoading;
} }


const PositionSearchWrapper: React.FC & SubComponents = async () => { const PositionSearchWrapper: React.FC & SubComponents = async () => {
const Positions = await fetchPositions();
const [Positions, abilities] = await Promise.all([fetchPositions(), getUserAbilities()]);
// const Positions:any[] = [] // const Positions:any[] = []


return <PositionSearch positions={Positions} />;
return <PositionSearch positions={Positions} abilities={abilities}/>;
}; };


PositionSearchWrapper.Loading = PositionSearchLoading; PositionSearchWrapper.Loading = PositionSearchLoading;


+ 6
- 3
src/components/SalarySearch/SalarySearch.tsx 查看文件

@@ -13,18 +13,21 @@ import FileUploadIcon from '@mui/icons-material/FileUpload';
import { exportSalary, importSalarys } from "@/app/api/salarys/actions"; import { exportSalary, importSalarys } from "@/app/api/salarys/actions";
import { downloadFile } from "@/app/utils/commonUtil"; import { downloadFile } from "@/app/utils/commonUtil";
import { errorDialog, successDialog } from "../Swal/CustomAlerts"; import { errorDialog, successDialog } from "../Swal/CustomAlerts";
import { MAINTAIN_SALARY } from "@/middleware";


interface Props { interface Props {
salarys: SalaryResult[]; salarys: SalaryResult[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<SalaryResult, "id">>; type SearchQuery = Partial<Omit<SalaryResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const SalarySearch: React.FC<Props> = ({ salarys }) => {
const SalarySearch: React.FC<Props> = ({ salarys, abilities }) => {
const { t } = useTranslation("salarys"); const { t } = useTranslation("salarys");


const [filteredSalarys, setFilteredSalarys] = useState(salarys); const [filteredSalarys, setFilteredSalarys] = useState(salarys);
const maintainSalary = abilities.includes(MAINTAIN_SALARY)


const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
@@ -109,7 +112,7 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => {


return ( return (
<> <>
<Stack
{maintainSalary && <Stack
direction="row" direction="row"
justifyContent="right" justifyContent="right"
flexWrap="wrap" flexWrap="wrap"
@@ -130,7 +133,7 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => {
{t("Export Salary")} {t("Export Salary")}
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</Stack>
</Stack>}
<SearchBox <SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={(query) => { onSearch={(query) => {


+ 3
- 2
src/components/SalarySearch/SalarySearchWrapper.tsx 查看文件

@@ -3,6 +3,7 @@ import React from "react";
import SalarySearch from "./SalarySearch"; import SalarySearch from "./SalarySearch";
import SalarySearchLoading from "./SalarySearchLoading"; import SalarySearchLoading from "./SalarySearchLoading";
import { fetchSalarys } from "@/app/api/salarys"; import { fetchSalarys } from "@/app/api/salarys";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof SalarySearchLoading; Loading: typeof SalarySearchLoading;
@@ -14,7 +15,7 @@ interface SubComponents {
// } // }


const SalarySearchWrapper: React.FC & SubComponents = async () => { const SalarySearchWrapper: React.FC & SubComponents = async () => {
const Salarys = await fetchSalarys();
const [Salarys, abilities] = await Promise.all([fetchSalarys(), getUserAbilities()]);
// const Salarys:any[] = [] // const Salarys:any[] = []
const salarysWithHourlyRate = Salarys.map((salary) => { const salarysWithHourlyRate = Salarys.map((salary) => {
// const hourlyRate = calculateHourlyRate(Number(salary.lowerLimit), Number(salary.upperLimit),22, 8) // const hourlyRate = calculateHourlyRate(Number(salary.lowerLimit), Number(salary.upperLimit),22, 8)
@@ -27,7 +28,7 @@ const SalarySearchWrapper: React.FC & SubComponents = async () => {
}) })
// console.log(salarysWithHourlyRate) // console.log(salarysWithHourlyRate)


return <SalarySearch salarys={salarysWithHourlyRate} />;
return <SalarySearch salarys={salarysWithHourlyRate} abilities={abilities}/>;
}; };


SalarySearchWrapper.Loading = SalarySearchLoading; SalarySearchWrapper.Loading = SalarySearchLoading;


+ 7
- 1
src/components/SkillSearch/SkillSearch.tsx 查看文件

@@ -9,15 +9,17 @@ import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { SkillResult } from "@/app/api/skill"; import { SkillResult } from "@/app/api/skill";
import { deleteSkill } from "@/app/api/skill/actions"; import { deleteSkill } from "@/app/api/skill/actions";
import { MAINTAIN_SKILL } from "@/middleware";


interface Props { interface Props {
skill: SkillResult[]; skill: SkillResult[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<SkillResult, "id">>; type SearchQuery = Partial<Omit<SkillResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const SkillSearch: React.FC<Props> = ({ skill }) => {
const SkillSearch: React.FC<Props> = ({ skill, abilities }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [filteredSkill, setFilteredSkill] = useState(skill); const [filteredSkill, setFilteredSkill] = useState(skill);
const router = useRouter(); const router = useRouter();
@@ -30,6 +32,8 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
const code = t("code") const code = t("code")
const description = t("description") const description = t("description")


const maintainSkill = abilities.includes(MAINTAIN_SKILL)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ {
@@ -73,6 +77,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
label: edit, label: edit,
onClick: onSkillClick, onClick: onSkillClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainSkill,
}, },
{ name: "name", label: name }, { name: "name", label: name },
{ name: "code", label: code }, { name: "code", label: code },
@@ -83,6 +88,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => {
onClick: deleteClick, onClick: deleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error", color: "error",
isHidden: !maintainSkill,
}, },
], ],
[t, onSkillClick, deleteClick] [t, onSkillClick, deleteClick]


+ 3
- 2
src/components/SkillSearch/SkillSearchWrapper.tsx 查看文件

@@ -2,15 +2,16 @@ import React from "react";
import SkillSearch from "./SkillSearch"; import SkillSearch from "./SkillSearch";
import SkillSearchLoading from "./SkillSearchLoading"; import SkillSearchLoading from "./SkillSearchLoading";
import { fetchSkill } from "@/app/api/skill"; import { fetchSkill } from "@/app/api/skill";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof SkillSearchLoading; Loading: typeof SkillSearchLoading;
} }


const SkillSearchWrapper: React.FC & SubComponents = async () => { const SkillSearchWrapper: React.FC & SubComponents = async () => {
const skill = await fetchSkill()
const [skill, abilities] = await Promise.all([fetchSkill(), getUserAbilities()])


return <SkillSearch skill={skill} />;
return <SkillSearch skill={skill} abilities={abilities}/>;
}; };


SkillSearchWrapper.Loading = SkillSearchLoading; SkillSearchWrapper.Loading = SkillSearchLoading;


+ 9
- 4
src/components/StaffSearch/StaffSearch.tsx 查看文件

@@ -10,7 +10,7 @@ import { deleteStaff } from "@/app/api/staff/actions";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import Person from '@mui/icons-material/Person'; import Person from '@mui/icons-material/Person';
import { MAINTAIN_USER, VIEW_USER } from "@/middleware";
import { MAINTAIN_STAFF, MAINTAIN_USER } from "@/middleware";
import { TeamResult } from "@/app/api/team"; import { TeamResult } from "@/app/api/team";
import { Grade } from "@/app/api/grades"; import { Grade } from "@/app/api/grades";
import { PositionResult } from "@/app/api/positions"; import { PositionResult } from "@/app/api/positions";
@@ -20,13 +20,13 @@ interface Props {
teams: TeamResult[] teams: TeamResult[]
grades: Grade[] grades: Grade[]
positions: PositionResult[] positions: PositionResult[]
isAuthed: boolean
abilities: String[]
} }


type SearchQuery = Partial<Omit<StaffResult, "id">>; type SearchQuery = Partial<Omit<StaffResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthed }) => {
const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, abilities }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(staff); const [filteredStaff, setFilteredStaff] = useState(staff);
const router = useRouter(); const router = useRouter();
@@ -35,6 +35,9 @@ const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthe
const gradeCombo = grades.map(g => `${g.name}`) const gradeCombo = grades.map(g => `${g.name}`)
const positionCombo = positions.map(p => `${p.name}`) const positionCombo = positions.map(p => `${p.name}`)


const maintainUser = abilities.includes(MAINTAIN_USER)
const maintainStaff = abilities.includes(MAINTAIN_STAFF)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ {
@@ -104,13 +107,14 @@ const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthe
label: t("Actions"), label: t("Actions"),
onClick: onStaffClick, onClick: onStaffClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainStaff,
}, },
{ {
name: "id", name: "id",
label: t("Users"), label: t("Users"),
onClick: onUserClick, onClick: onUserClick,
buttonIcon: <Person />, buttonIcon: <Person />,
isHidden: isAuthed,
isHidden: !maintainUser,
}, },
{ name: "team", label: t("Team") }, { name: "team", label: t("Team") },
{ name: "name", label: t("Staff Name") }, { name: "name", label: t("Staff Name") },
@@ -123,6 +127,7 @@ const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthe
onClick: deleteClick, onClick: deleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error", color: "error",
isHidden: !maintainStaff,
}, },
], ],
[t, onStaffClick, deleteClick] [t, onStaffClick, deleteClick]


+ 3
- 3
src/components/StaffSearch/StaffSearchWrapper.tsx 查看文件

@@ -7,7 +7,7 @@ import { authOptions } from "@/config/authConfig";
import { fetchTeam } from "@/app/api/team"; import { fetchTeam } from "@/app/api/team";
import { fetchPositions } from "@/app/api/positions"; import { fetchPositions } from "@/app/api/positions";
import { fetchGrades } from "@/app/api/grades"; import { fetchGrades } from "@/app/api/grades";
import { MAINTAIN_USER, VIEW_USER } from "@/middleware";
import { MAINTAIN_USER } from "@/middleware";


interface SubComponents { interface SubComponents {
Loading: typeof StaffSearchLoading; Loading: typeof StaffSearchLoading;
@@ -19,14 +19,14 @@ interface SessionWithAbilities extends Session {


const StaffSearchWrapper: React.FC & SubComponents = async () => { const StaffSearchWrapper: React.FC & SubComponents = async () => {
const session = await getServerSession(authOptions) as SessionWithAbilities; const session = await getServerSession(authOptions) as SessionWithAbilities;
const isAuthed = ![MAINTAIN_USER, VIEW_USER].some((ability) => session.abilities!.includes(ability))
const abilities = session.abilities!


const staff = await fetchStaff(); const staff = await fetchStaff();
const teams = await fetchTeam(); const teams = await fetchTeam();
const grades = await fetchGrades(); const grades = await fetchGrades();
const positions = await fetchPositions(); const positions = await fetchPositions();


return <StaffSearch staff={staff} teams={teams} grades={grades} positions={positions} isAuthed={isAuthed}/>;
return <StaffSearch staff={staff} teams={teams} grades={grades} positions={positions} abilities={abilities}/>;
}; };


StaffSearchWrapper.Loading = StaffSearchLoading; StaffSearchWrapper.Loading = StaffSearchLoading;


+ 7
- 2
src/components/SubsidiarySearch/SubsidiarySearch.tsx 查看文件

@@ -10,18 +10,21 @@ import { useRouter, useSearchParams } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { Subsidiary } from "@/app/api/subsidiary"; import { Subsidiary } from "@/app/api/subsidiary";
import { deleteSubsidiary } from "@/app/api/subsidiary/actions"; import { deleteSubsidiary } from "@/app/api/subsidiary/actions";
import { MAINTAIN_SUBSIDIARY } from "@/middleware";


interface Props { interface Props {
subsidiaries: Subsidiary[]; subsidiaries: Subsidiary[];
abilities: String[];
} }


type SearchQuery = Partial<Omit<Subsidiary, "id">>; type SearchQuery = Partial<Omit<Subsidiary, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => {
const SubsidiarySearch: React.FC<Props> = ({ subsidiaries, abilities }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const maintainSubsidiary = abilities.includes(MAINTAIN_SUBSIDIARY)


const [filteredSubsidiaries, setFilteredSubsidiaries] = useState(subsidiaries); const [filteredSubsidiaries, setFilteredSubsidiaries] = useState(subsidiaries);
useEffect(() => { useEffect(() => {
@@ -63,6 +66,7 @@ const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => {
label: t("Details"), label: t("Details"),
onClick: onTaskClick, onClick: onTaskClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainSubsidiary,
}, },
{ name: "code", label: t("Subsidiary Code") }, { name: "code", label: t("Subsidiary Code") },
{ name: "name", label: t("Subsidiary Name") }, { name: "name", label: t("Subsidiary Name") },
@@ -71,7 +75,8 @@ const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => {
label: t("Delete"), label: t("Delete"),
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainSubsidiary,
}, },
], ],
[onTaskClick, t], [onTaskClick, t],


+ 3
- 2
src/components/SubsidiarySearch/SubsidiarySearchWrapper.tsx 查看文件

@@ -2,15 +2,16 @@ import React from "react";
import SubsidiarySearch from "./SubsidiarySearch"; import SubsidiarySearch from "./SubsidiarySearch";
import SubsidiarySearchLoading from "./SubsidiarySearchLoading"; import SubsidiarySearchLoading from "./SubsidiarySearchLoading";
import { fetchAllSubsidiaries } from "@/app/api/subsidiary"; import { fetchAllSubsidiaries } from "@/app/api/subsidiary";
import { getUserAbilities } from "@/app/utils/commonUtil";


interface SubComponents { interface SubComponents {
Loading: typeof SubsidiarySearchLoading; Loading: typeof SubsidiarySearchLoading;
} }


const SubsidiarySearchWrapper: React.FC & SubComponents = async () => { const SubsidiarySearchWrapper: React.FC & SubComponents = async () => {
const subsidiaries = await fetchAllSubsidiaries();
const [subsidiaries, abilities] = await Promise.all([fetchAllSubsidiaries(), getUserAbilities()]);


return <SubsidiarySearch subsidiaries={subsidiaries} />;
return <SubsidiarySearch subsidiaries={subsidiaries} abilities={abilities} />;
}; };


SubsidiarySearchWrapper.Loading = SubsidiarySearchLoading; SubsidiarySearchWrapper.Loading = SubsidiarySearchLoading;


+ 8
- 2
src/components/TeamSearch/TeamSearch.tsx 查看文件

@@ -10,14 +10,16 @@ import DeleteIcon from "@mui/icons-material/Delete";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { deleteTeam } from "@/app/api/team/actions"; import { deleteTeam } from "@/app/api/team/actions";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { MAINTAIN_TEAM } from "@/middleware";


interface Props { interface Props {
team: TeamResult[]; team: TeamResult[];
abilities: String[];
} }
type SearchQuery = Partial<Omit<TeamResult, "id">>; type SearchQuery = Partial<Omit<TeamResult, "id">>;
type SearchParamNames = keyof SearchQuery; type SearchParamNames = keyof SearchQuery;


const TeamSearch: React.FC<Props> = ({ team }) => {
const TeamSearch: React.FC<Props> = ({ team, abilities }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [filteredTeam, setFilteredTeam] = useState(team); const [filteredTeam, setFilteredTeam] = useState(team);
const router = useRouter(); const router = useRouter();
@@ -29,6 +31,8 @@ const TeamSearch: React.FC<Props> = ({ team }) => {
const teamLead = t("teamLead") const teamLead = t("teamLead")
const delete_t = t("delete") const delete_t = t("delete")


const maintainTeam = abilities.includes(MAINTAIN_TEAM)

const searchCriteria: Criterion<SearchParamNames>[] = useMemo( const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [ () => [
{ {
@@ -77,6 +81,7 @@ const TeamSearch: React.FC<Props> = ({ team }) => {
label: edit, label: edit,
onClick: onTeamClick, onClick: onTeamClick,
buttonIcon: <EditNote />, buttonIcon: <EditNote />,
isHidden: !maintainTeam,
}, },
{ name: "name", label: name }, { name: "name", label: name },
{ name: "code", label: code }, { name: "code", label: code },
@@ -87,7 +92,8 @@ const TeamSearch: React.FC<Props> = ({ team }) => {
label: delete_t, label: delete_t,
onClick: onDeleteClick, onClick: onDeleteClick,
buttonIcon: <DeleteIcon />, buttonIcon: <DeleteIcon />,
color: "error"
color: "error",
isHidden: !maintainTeam,
}, },
], ],
[t] [t]


+ 3
- 2
src/components/TeamSearch/TeamSearchWrapper.tsx 查看文件

@@ -3,6 +3,7 @@ import React from "react";
import TeamSearch from "./TeamSearch"; import TeamSearch from "./TeamSearch";
import TeamSearchLoading from "./TeamSearchLoading"; import TeamSearchLoading from "./TeamSearchLoading";
import { fetchTeam } from "@/app/api/team"; import { fetchTeam } from "@/app/api/team";
import { getUserAbilities } from "@/app/utils/commonUtil";
// import { preloadTeam } from "@/app/api/Team"; // import { preloadTeam } from "@/app/api/Team";


interface SubComponents { interface SubComponents {
@@ -10,10 +11,10 @@ interface SubComponents {
} }


const TeamSearchWrapper: React.FC & SubComponents = async () => { const TeamSearchWrapper: React.FC & SubComponents = async () => {
const Team = await fetchTeam();
const [Team, abilities] = await Promise.all([fetchTeam(), getUserAbilities()]);
console.log(Team); console.log(Team);


return <TeamSearch team={Team} />;
return <TeamSearch team={Team} abilities={abilities}/>;
}; };


TeamSearchWrapper.Loading = TeamSearchLoading; TeamSearchWrapper.Loading = TeamSearchLoading;


+ 143
- 35
src/middleware.ts 查看文件

@@ -11,22 +11,41 @@ export const [
NORMAL_STAFF, NORMAL_STAFF,
SUPPORTING_STAFF SUPPORTING_STAFF
] = [ ] = [
"Super Admin",
"Top Management",
"Team Leader",
"Normal Staff",
"Supporting Staff"
]
"Super Admin",
"Top Management",
"Team Leader",
"Normal Staff",
"Supporting Staff"
]


// abilities // abilities
export const [ export const [
VIEW_USER,
MAINTAIN_USER, MAINTAIN_USER,
MAINTAIN_TIMESHEET, MAINTAIN_TIMESHEET,
VIEW_TASK_TEMPLATE, VIEW_TASK_TEMPLATE,
VIEW_GROUP, VIEW_GROUP,
VIEW_MASTERDATA,
MAINTAIN_MASTERDATA,
VIEW_CLIENT,
VIEW_SUBSIDIARY,
VIEW_STAFF,
VIEW_COMPANY,
VIEW_SKILL,
VIEW_DEPARTMENT,
VIEW_POSITION,
VIEW_SALARY,
VIEW_TEAM,
VIEW_USER_GROUP,
VIEW_HOLIDAY,
MAINTAIN_CLIENT,
MAINTAIN_SUBSIDIARY,
MAINTAIN_STAFF,
MAINTAIN_COMPANY,
MAINTAIN_SKILL,
MAINTAIN_DEPARTMENT,
MAINTAIN_POSITION,
MAINTAIN_SALARY,
MAINTAIN_TEAM,
MAINTAIN_USER_GROUP,
MAINTAIN_HOLIDAY,
VIEW_DASHBOARD_SELF, VIEW_DASHBOARD_SELF,
VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_ALL,
IMPORT_INVOICE, IMPORT_INVOICE,
@@ -41,27 +60,46 @@ export const [
DELETE_PROJECT, DELETE_PROJECT,
MAINTAIN_TIMESHEET_FAST_TIME_ENTRY, MAINTAIN_TIMESHEET_FAST_TIME_ENTRY,
] = [ ] = [
'VIEW_USER',
'MAINTAIN_USER',
'MAINTAIN_TIMESHEET',
'VIEW_TASK_TEMPLATE',
'VIEW_GROUP',
'VIEW_MASTERDATA',
'MAINTAIN_MASTERDATA',
'VIEW_DASHBOARD_SELF',
'VIEW_DASHBOARD_ALL',
'IMPORT_INVOICE',
'MAINTAIN_GROUP',
'GENERATE_REPORTS',
'VIEW_STAFF_PROFILE',
'IMPORT_RECEIPT',
'MAINTAIN_TASK_TEMPLATE',
'MAINTAIN_TIMESHEET_7DAYS',
'VIEW_PROJECT',
'MAINTAIN_PROJECT',
'DELETE_PROJECT',
'MAINTAIN_TIMESHEET_FAST_TIME_ENTRY'
]
'MAINTAIN_USER',
'MAINTAIN_TIMESHEET',
'VIEW_TASK_TEMPLATE',
'VIEW_GROUP',
'VIEW_CLIENT',
'VIEW_SUBSIDIARY',
'VIEW_STAFF',
'VIEW_COMPANY',
'VIEW_SKILL',
'VIEW_DEPARTMENT',
'VIEW_POSITION',
'VIEW_SALARY',
'VIEW_TEAM',
'VIEW_USER_GROUP',
'VIEW_HOLIDAY',
'MAINTAIN_CLIENT',
'MAINTAIN_SUBSIDIARY',
'MAINTAIN_STAFF',
'MAINTAIN_COMPANY',
'MAINTAIN_SKILL',
'MAINTAIN_DEPARTMENT',
'MAINTAIN_POSITION',
'MAINTAIN_SALARY',
'MAINTAIN_TEAM',
'MAINTAIN_USER_GROUP',
'MAINTAIN_HOLIDAY',
'VIEW_DASHBOARD_SELF',
'VIEW_DASHBOARD_ALL',
'IMPORT_INVOICE',
'MAINTAIN_GROUP',
'GENERATE_REPORTS',
'VIEW_STAFF_PROFILE',
'IMPORT_RECEIPT',
'MAINTAIN_TASK_TEMPLATE',
'MAINTAIN_TIMESHEET_7DAYS',
'VIEW_PROJECT',
'MAINTAIN_PROJECT',
'DELETE_PROJECT',
'MAINTAIN_TIMESHEET_FAST_TIME_ENTRY'
]


const PRIVATE_ROUTES = [ const PRIVATE_ROUTES = [
"/analytics", "/analytics",
@@ -93,7 +131,7 @@ export default async function middleware(
const authMiddleware = withAuth({ const authMiddleware = withAuth({
pages: authOptions.pages, pages: authOptions.pages,
callbacks: { callbacks: {
authorized: ({req, token}) => {
authorized: ({ req, token }) => {
let isAuth = Boolean(token); let isAuth = Boolean(token);
if (!Boolean(token)) { if (!Boolean(token)) {
return Boolean(token) return Boolean(token)
@@ -109,26 +147,96 @@ export default async function middleware(
} }


if (req.nextUrl.pathname.startsWith('/settings')) { if (req.nextUrl.pathname.startsWith('/settings')) {
isAuth = [VIEW_MASTERDATA, MAINTAIN_MASTERDATA].some((ability) => abilities.includes(ability));
}
isAuth = [
VIEW_CLIENT,
VIEW_SUBSIDIARY,
VIEW_STAFF,
VIEW_COMPANY,
VIEW_SKILL,
VIEW_DEPARTMENT,
VIEW_POSITION,
VIEW_SALARY,
VIEW_TEAM,
VIEW_USER_GROUP,
VIEW_HOLIDAY,
MAINTAIN_CLIENT,
MAINTAIN_SUBSIDIARY,
MAINTAIN_STAFF,
MAINTAIN_COMPANY,
MAINTAIN_SKILL,
MAINTAIN_DEPARTMENT,
MAINTAIN_POSITION,
MAINTAIN_SALARY,
MAINTAIN_TEAM,
MAINTAIN_USER_GROUP,
MAINTAIN_HOLIDAY
].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/customer/create') || req.nextUrl.pathname.startsWith('/settings/customer/edit')) {
isAuth = [MAINTAIN_CLIENT].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/subsidiary/create') || req.nextUrl.pathname.startsWith('/settings/subsidiary/edit')) {
isAuth = [MAINTAIN_SUBSIDIARY].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/staff/create') || req.nextUrl.pathname.startsWith('/settings/staff/edit')) {
isAuth = [MAINTAIN_STAFF].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/company/create') || req.nextUrl.pathname.startsWith('/settings/company/edit')) {
isAuth = [MAINTAIN_COMPANY].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/skill/create') || req.nextUrl.pathname.startsWith('/settings/skill/edit')) {
isAuth = [MAINTAIN_SKILL].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/department/create') || req.nextUrl.pathname.startsWith('/settings/department/edit')) {
isAuth = [MAINTAIN_DEPARTMENT].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/position/create') || req.nextUrl.pathname.startsWith('/settings/position/edit')) {
isAuth = [MAINTAIN_POSITION].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/team/create') || req.nextUrl.pathname.startsWith('/settings/team/edit')) {
isAuth = [MAINTAIN_TEAM].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/group/create') || req.nextUrl.pathname.startsWith('/settings/group/edit')) {
isAuth = [MAINTAIN_USER_GROUP].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/holiday/create') || req.nextUrl.pathname.startsWith('/settings/holiday/edit')) {
isAuth = [MAINTAIN_HOLIDAY].some((ability) => abilities.includes(ability));
}

if (req.nextUrl.pathname.startsWith('/settings/user')) { if (req.nextUrl.pathname.startsWith('/settings/user')) {
isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability));
isAuth = [MAINTAIN_USER].some((ability) => abilities.includes(ability));
} }

if (req.nextUrl.pathname.startsWith('/settings/staff/user')) { if (req.nextUrl.pathname.startsWith('/settings/staff/user')) {
isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability));
isAuth = [MAINTAIN_USER].some((ability) => abilities.includes(ability));
} }

if (req.nextUrl.pathname.startsWith('/analytics')) { if (req.nextUrl.pathname.startsWith('/analytics')) {
isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability)); isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability));
} }

if (req.nextUrl.pathname.startsWith('/settings/staff/edit')) { if (req.nextUrl.pathname.startsWith('/settings/staff/edit')) {
isAuth = [VIEW_STAFF_PROFILE].some((ability) => abilities.includes(ability)); isAuth = [VIEW_STAFF_PROFILE].some((ability) => abilities.includes(ability));
} }
if (req.nextUrl.pathname.startsWith('/invoice')) { if (req.nextUrl.pathname.startsWith('/invoice')) {
isAuth = [IMPORT_INVOICE, IMPORT_RECEIPT].some((ability) => abilities.includes(ability)); isAuth = [IMPORT_INVOICE, IMPORT_RECEIPT].some((ability) => abilities.includes(ability));
} }

if (req.nextUrl.pathname.startsWith('/dashboard')) { if (req.nextUrl.pathname.startsWith('/dashboard')) {
isAuth = [VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => abilities.includes(ability)); isAuth = [VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => abilities.includes(ability));
} }

return isAuth return isAuth
} }
} }


Loading…
取消
儲存