| @@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography"; | |||
| import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| import { fetchCompanys, preloadCompanys } from "@/app/api/companys"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import { MAINTAIN_COMPANY } from "@/middleware"; | |||
| export const metadata: Metadata = { | |||
| title: "Company", | |||
| @@ -20,6 +22,9 @@ const Company: React.FC = async () => { | |||
| fetchCompanys(); | |||
| preloadCompanys(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainCompany = abilities.includes(MAINTAIN_COMPANY) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -31,14 +36,14 @@ const Company: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Company")} | |||
| </Typography> | |||
| <Button | |||
| {maintainCompany && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/company/create" | |||
| > | |||
| {t("Create Company")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <Suspense fallback={<CompanySearch.Loading />}> | |||
| <CompanySearch/> | |||
| @@ -9,6 +9,8 @@ import { Metadata } from "next"; | |||
| import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import { MAINTAIN_CLIENT } from "@/middleware"; | |||
| export const metadata: Metadata = { | |||
| title: "Customer", | |||
| @@ -17,6 +19,8 @@ export const metadata: Metadata = { | |||
| const Customer: React.FC = async () => { | |||
| const { t } = await getServerI18n("customer"); | |||
| preloadAllCustomers(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainClient = abilities.includes(MAINTAIN_CLIENT) | |||
| return ( | |||
| <> | |||
| @@ -29,14 +33,14 @@ const Customer: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Customer")} | |||
| </Typography> | |||
| <Button | |||
| {maintainClient && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/customer/create" | |||
| > | |||
| {t("Create Customer")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <I18nProvider namespaces={["customer", "common"]}> | |||
| <Suspense fallback={<CustomerSearch.Loading />}> | |||
| @@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography"; | |||
| import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| import { fetchDepartments, preloadDepartments } from "@/app/api/departments"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import { MAINTAIN_DEPARTMENT } from "@/middleware"; | |||
| export const metadata: Metadata = { | |||
| title: "Department", | |||
| @@ -20,6 +22,9 @@ const Department: React.FC = async () => { | |||
| // fetchDepartments(); | |||
| // preloadDepartments(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainDepartment = abilities.includes(MAINTAIN_DEPARTMENT) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -31,14 +36,14 @@ const Department: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Department")} | |||
| </Typography> | |||
| <Button | |||
| {maintainDepartment && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/department/new" | |||
| > | |||
| {t("Create Department")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <Suspense fallback={<DepartmentSearch.Loading />}> | |||
| <DepartmentSearch/> | |||
| @@ -8,6 +8,8 @@ import Typography from "@mui/material/Typography"; | |||
| import Link from "next/link"; | |||
| import { Suspense } from "react"; | |||
| import { fetchPositions, preloadPositions } from "@/app/api/positions"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import { MAINTAIN_POSITION } from "@/middleware"; | |||
| export const metadata: Metadata = { | |||
| title: "Position", | |||
| @@ -20,6 +22,9 @@ const Position: React.FC = async () => { | |||
| // fetchPositions(); | |||
| // preloadPositions(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainPosition = abilities.includes(MAINTAIN_POSITION) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -31,14 +36,14 @@ const Position: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Position")} | |||
| </Typography> | |||
| <Button | |||
| {maintainPosition && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/position/new" | |||
| > | |||
| {t("Create Position")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <Suspense fallback={<PositionSearch.Loading />}> | |||
| <PositionSearch/> | |||
| @@ -1,7 +1,9 @@ | |||
| import { preloadClaims } from "@/app/api/claims"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| // import { preloadSkill, preloadTeamLeads } from "@/app/api/staff"; | |||
| import SkillSearch from "@/components/SkillSearch"; | |||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||
| import { MAINTAIN_SKILL } from "@/middleware"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| import Stack from "@mui/material/Stack"; | |||
| @@ -18,6 +20,10 @@ const Skill: React.FC = async () => { | |||
| const { t } = await getServerI18n("skill"); | |||
| // preloadTeamLeads(); | |||
| // preloadSkill(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainSkill = abilities.includes(MAINTAIN_SKILL) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -29,14 +35,14 @@ const Skill: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Skill")} | |||
| </Typography> | |||
| <Button | |||
| {maintainSkill && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/skill/create" | |||
| > | |||
| {t("Create Skill")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <I18nProvider namespaces={["skill", "common"]}> | |||
| <Suspense fallback={<SkillSearch.Loading />}> | |||
| @@ -1,7 +1,9 @@ | |||
| import { preloadClaims } from "@/app/api/claims"; | |||
| import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import StaffSearch from "@/components/StaffSearch"; | |||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||
| import { MAINTAIN_STAFF } from "@/middleware"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| import Stack from "@mui/material/Stack"; | |||
| @@ -18,6 +20,10 @@ const Staff: React.FC = async () => { | |||
| const { t } = await getServerI18n("staff"); | |||
| preloadTeamLeads(); | |||
| preloadStaff(); | |||
| const abilities = await getUserAbilities(); | |||
| const maintainStaff = abilities.includes(MAINTAIN_STAFF) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -29,14 +35,14 @@ const Staff: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Staff")} | |||
| </Typography> | |||
| <Button | |||
| {maintainStaff && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/staff/create" | |||
| > | |||
| {t("Create Staff")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <I18nProvider namespaces={["staff", "common"]}> | |||
| <Suspense fallback={<StaffSearch.Loading />}> | |||
| @@ -9,6 +9,8 @@ import { Suspense } from "react"; | |||
| import { I18nProvider } from "@/i18n"; | |||
| import { preloadAllSubsidiaries } from "@/app/api/subsidiary"; | |||
| import SubsidiarySearch from "@/components/SubsidiarySearch"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import { MAINTAIN_SUBSIDIARY } from "@/middleware"; | |||
| export const metadata: Metadata = { | |||
| title: "Subsidiary", | |||
| @@ -17,6 +19,8 @@ export const metadata: Metadata = { | |||
| const Subsidiary: React.FC = async () => { | |||
| const { t } = await getServerI18n("subsidiary"); | |||
| preloadAllSubsidiaries(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainSubsidiary = abilities.includes(MAINTAIN_SUBSIDIARY) | |||
| return ( | |||
| <> | |||
| @@ -29,14 +33,14 @@ const Subsidiary: React.FC = async () => { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Subsidiary")} | |||
| </Typography> | |||
| <Button | |||
| {maintainSubsidiary && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/subsidiary/create" | |||
| > | |||
| {t("Create Subsidiary")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <I18nProvider namespaces={["subsidiary", "common"]}> | |||
| <Suspense fallback={<SubsidiarySearch.Loading />}> | |||
| @@ -1,8 +1,10 @@ | |||
| import { preloadClaims } from "@/app/api/claims"; | |||
| import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| import StaffSearch from "@/components/StaffSearch"; | |||
| import TeamSearch from "@/components/TeamSearch"; | |||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||
| import { MAINTAIN_TEAM } from "@/middleware"; | |||
| import Add from "@mui/icons-material/Add"; | |||
| import Button from "@mui/material/Button"; | |||
| import Stack from "@mui/material/Stack"; | |||
| @@ -21,6 +23,10 @@ export const metadata: Metadata = { | |||
| const { t } = await getServerI18n("team"); | |||
| // preloadTeamLeads(); | |||
| // preloadStaff(); | |||
| const abilities = await getUserAbilities() | |||
| const maintainTeam = abilities.includes(MAINTAIN_TEAM) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -32,14 +38,14 @@ export const metadata: Metadata = { | |||
| <Typography variant="h4" marginInlineEnd={2}> | |||
| {t("Team")} | |||
| </Typography> | |||
| <Button | |||
| {maintainTeam && <Button | |||
| variant="contained" | |||
| startIcon={<Add />} | |||
| LinkComponent={Link} | |||
| href="/settings/team/create" | |||
| > | |||
| {t("Create Team")} | |||
| </Button> | |||
| </Button>} | |||
| </Stack> | |||
| <I18nProvider namespaces={["team", "common"]}> | |||
| <Suspense fallback={<TeamSearch.Loading />}> | |||
| @@ -23,16 +23,19 @@ import { | |||
| import { useRouter } from "next/navigation"; | |||
| import { deleteDialog, submitDialog } from "../Swal/CustomAlerts"; | |||
| import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; | |||
| import { MAINTAIN_HOLIDAY } from "@/middleware"; | |||
| interface Props { | |||
| holidays: HolidaysList[]; | |||
| abilities: String[]; | |||
| } | |||
| const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||
| const CompanyHoliday: React.FC<Props> = ({ holidays, abilities }) => { | |||
| const { t } = useTranslation("holidays"); | |||
| const router = useRouter(); | |||
| const formValues = useFormContext(); | |||
| const [serverError, setServerError] = useState(""); | |||
| const maintainHoliday = abilities.includes(MAINTAIN_HOLIDAY) | |||
| const companyHolidays = useMemo( | |||
| () => [...getPublicHolidaysForNYears(2), ...holidays], | |||
| @@ -139,7 +142,7 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||
| }} | |||
| /> | |||
| <CompanyHolidayDialog | |||
| open={open} | |||
| open={maintainHoliday && open} | |||
| onClose={handleClose} | |||
| title={ | |||
| !editable | |||
| @@ -6,6 +6,7 @@ import { fetchCompanys } from "@/app/api/companys"; | |||
| import Holidays from "date-holidays"; | |||
| import { HolidaysResult, fetchHolidays, HolidaysList } from "@/app/api/holidays"; | |||
| import { convertDateArrayToString } from "@/app/utils/formatUtil"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof CompanyHolidayLoading; | |||
| @@ -14,7 +15,7 @@ interface SubComponents { | |||
| const CompanyHolidayWrapper: React.FC & SubComponents = async () => { | |||
| // const Companys = await fetchCompanys(); | |||
| const companyHolidays: HolidaysResult[] = await fetchHolidays() | |||
| const [companyHolidays, abilities] = await Promise.all([fetchHolidays(), getUserAbilities()]) | |||
| // console.log(companyHolidays) | |||
| 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; | |||
| @@ -10,21 +10,25 @@ import { useRouter } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { deleteCompany } from "@/app/api/companys/actions"; | |||
| import DeleteIcon from '@mui/icons-material/Delete'; | |||
| import { MAINTAIN_COMPANY } from "@/middleware"; | |||
| interface Props { | |||
| companys: CompanyResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<CompanyResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const CompanySearch: React.FC<Props> = ({ companys }) => { | |||
| const CompanySearch: React.FC<Props> = ({ companys, abilities }) => { | |||
| const { t } = useTranslation("companys"); | |||
| const router = useRouter() | |||
| const [filteredCompanys, setFilteredCompanys] = useState(companys); | |||
| const maintainCompany = abilities.includes(MAINTAIN_COMPANY) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { label: t("Company code"), paramName: "companyCode", type: "text" }, | |||
| @@ -62,6 +66,7 @@ const CompanySearch: React.FC<Props> = ({ companys }) => { | |||
| label: t("Details"), | |||
| onClick: onProjectClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainCompany, | |||
| }, | |||
| { name: "companyCode", label: t("Company Code") }, | |||
| { name: "name", label: t("Company Name") }, | |||
| @@ -74,7 +79,8 @@ const CompanySearch: React.FC<Props> = ({ companys }) => { | |||
| label: t("Delete"), | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainCompany, | |||
| }, | |||
| ], | |||
| [t, onProjectClick], | |||
| @@ -3,6 +3,7 @@ import React from "react"; | |||
| import CompanySearch from "./CompanySearch"; | |||
| import CompanySearchLoading from "./CompanySearchLoading"; | |||
| import { fetchCompanys } from "@/app/api/companys"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof CompanySearchLoading; | |||
| @@ -10,9 +11,9 @@ interface SubComponents { | |||
| const CompanySearchWrapper: React.FC & SubComponents = async () => { | |||
| // 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; | |||
| @@ -10,18 +10,21 @@ import DeleteIcon from '@mui/icons-material/Delete'; | |||
| import { useRouter, useSearchParams } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { deleteCustomer } from "@/app/api/customer/actions"; | |||
| import { MAINTAIN_CLIENT } from "@/middleware"; | |||
| interface Props { | |||
| customers: Customer[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<Customer, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const CustomerSearch: React.FC<Props> = ({ customers }) => { | |||
| const CustomerSearch: React.FC<Props> = ({ customers, abilities }) => { | |||
| const { t } = useTranslation(); | |||
| const router = useRouter() | |||
| const searchParams = useSearchParams() | |||
| const maintainClient = abilities.includes(MAINTAIN_CLIENT) | |||
| const [filteredCustomers, setFilteredCustomers] = useState(customers); | |||
| useEffect(() => { | |||
| @@ -60,6 +63,7 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => { | |||
| label: t("Details"), | |||
| onClick: onTaskClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainClient | |||
| }, | |||
| { name: "code", label: t("Customer Code") }, | |||
| { name: "name", label: t("Customer Name") }, | |||
| @@ -68,7 +72,8 @@ const CustomerSearch: React.FC<Props> = ({ customers }) => { | |||
| label: t("Delete"), | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainClient | |||
| }, | |||
| ], | |||
| [onTaskClick, t], | |||
| @@ -2,15 +2,16 @@ import { fetchAllCustomers } from "@/app/api/customer"; | |||
| import React from "react"; | |||
| import CustomerSearch from "./CustomerSearch"; | |||
| import CustomerSearchLoading from "./CustomerSearchLoading"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof CustomerSearchLoading; | |||
| } | |||
| 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; | |||
| @@ -10,19 +10,22 @@ import { useRouter } from "next/navigation"; | |||
| import DeleteIcon from '@mui/icons-material/Delete'; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { deleteDepartment } from "@/app/api/departments/actions"; | |||
| import { MAINTAIN_DEPARTMENT } from "@/middleware"; | |||
| interface Props { | |||
| departments: DepartmentResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<DepartmentResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
| const DepartmentSearch: React.FC<Props> = ({ departments, abilities }) => { | |||
| const { t } = useTranslation("departments"); | |||
| const router = useRouter(); | |||
| const [filteredDepartments, setFilteredDepartments] = useState(departments); | |||
| const maintainDepartment = abilities.includes(MAINTAIN_DEPARTMENT) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| @@ -60,6 +63,7 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
| label: t("Details"), | |||
| onClick: onProjectClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainDepartment, | |||
| }, | |||
| { name: "code", label: t("Department Code") }, | |||
| { name: "name", label: t("Department Name") }, | |||
| @@ -69,7 +73,8 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
| label: t("Delete"), | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainDepartment, | |||
| }, | |||
| ], | |||
| [t, onProjectClick], | |||
| @@ -3,16 +3,17 @@ import React from "react"; | |||
| import DepartmentSearch from "./DepartmentSearch"; | |||
| import DepartmentSearchLoading from "./DepartmentSearchLoading"; | |||
| import { fetchDepartments } from "@/app/api/departments"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof DepartmentSearchLoading; | |||
| } | |||
| const DepartmentSearchWrapper: React.FC & SubComponents = async () => { | |||
| const Departments = await fetchDepartments(); | |||
| const [Departments, abilities] = await Promise.all([fetchDepartments(), getUserAbilities()]); | |||
| // const Departments:any[] = [] | |||
| return <DepartmentSearch departments={Departments} />; | |||
| return <DepartmentSearch departments={Departments} abilities={abilities} />; | |||
| }; | |||
| DepartmentSearchWrapper.Loading = DepartmentSearchLoading; | |||
| @@ -39,15 +39,34 @@ import { | |||
| GENERATE_REPORTS, | |||
| IMPORT_INVOICE, | |||
| IMPORT_RECEIPT, | |||
| MAINTAIN_MASTERDATA, | |||
| MAINTAIN_PROJECT, | |||
| MAINTAIN_TASK_TEMPLATE, | |||
| MAINTAIN_USER, | |||
| VIEW_DASHBOARD_ALL, | |||
| VIEW_DASHBOARD_SELF, | |||
| VIEW_MASTERDATA, | |||
| 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"; | |||
| import { SessionWithAbilities } from "../AppBar/NavigationToggle"; | |||
| import { authOptions } from "@/config/authConfig"; | |||
| @@ -217,35 +236,101 @@ const NavigationContent: React.FC<Props> = ({ abilities, username }) => { | |||
| icon: <Settings />, | |||
| label: "Setting", | |||
| 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), | |||
| ), | |||
| 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 />, | |||
| label: "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 />, | |||
| label: "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 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" }, | |||
| ], | |||
| }, | |||
| ]; | |||
| @@ -10,19 +10,22 @@ import { useRouter } from "next/navigation"; | |||
| import DeleteIcon from '@mui/icons-material/Delete'; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { deletePosition } from "@/app/api/positions/actions"; | |||
| import { MAINTAIN_POSITION } from "@/middleware"; | |||
| interface Props { | |||
| positions: PositionResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<PositionResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const PositionSearch: React.FC<Props> = ({ positions }) => { | |||
| const PositionSearch: React.FC<Props> = ({ positions, abilities }) => { | |||
| const { t } = useTranslation("positions"); | |||
| const router = useRouter(); | |||
| const [filteredPositions, setFilteredPositions] = useState(positions); | |||
| const maintainPosition = abilities.includes(MAINTAIN_POSITION) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| @@ -61,6 +64,7 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||
| label: t("Details"), | |||
| onClick: onPositionClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainPosition, | |||
| }, | |||
| { name: "code", label: t("Position Code") }, | |||
| { name: "name", label: t("Position Name") }, | |||
| @@ -70,7 +74,8 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||
| label: t("Delete"), | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainPosition, | |||
| }, | |||
| ], | |||
| [t, onPositionClick], | |||
| @@ -3,16 +3,17 @@ import React from "react"; | |||
| import PositionSearch from "./PositionSearch"; | |||
| import PositionSearchLoading from "./PositionSearchLoading"; | |||
| import { fetchPositions } from "@/app/api/positions"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof PositionSearchLoading; | |||
| } | |||
| const PositionSearchWrapper: React.FC & SubComponents = async () => { | |||
| const Positions = await fetchPositions(); | |||
| const [Positions, abilities] = await Promise.all([fetchPositions(), getUserAbilities()]); | |||
| // const Positions:any[] = [] | |||
| return <PositionSearch positions={Positions} />; | |||
| return <PositionSearch positions={Positions} abilities={abilities}/>; | |||
| }; | |||
| PositionSearchWrapper.Loading = PositionSearchLoading; | |||
| @@ -13,18 +13,21 @@ import FileUploadIcon from '@mui/icons-material/FileUpload'; | |||
| import { exportSalary, importSalarys } from "@/app/api/salarys/actions"; | |||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||
| import { errorDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { MAINTAIN_SALARY } from "@/middleware"; | |||
| interface Props { | |||
| salarys: SalaryResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<SalaryResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||
| const SalarySearch: React.FC<Props> = ({ salarys, abilities }) => { | |||
| const { t } = useTranslation("salarys"); | |||
| const [filteredSalarys, setFilteredSalarys] = useState(salarys); | |||
| const maintainSalary = abilities.includes(MAINTAIN_SALARY) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| @@ -109,7 +112,7 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||
| return ( | |||
| <> | |||
| <Stack | |||
| {maintainSalary && <Stack | |||
| direction="row" | |||
| justifyContent="right" | |||
| flexWrap="wrap" | |||
| @@ -130,7 +133,7 @@ const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||
| {t("Export Salary")} | |||
| </Button> | |||
| </ButtonGroup> | |||
| </Stack> | |||
| </Stack>} | |||
| <SearchBox | |||
| criteria={searchCriteria} | |||
| onSearch={(query) => { | |||
| @@ -3,6 +3,7 @@ import React from "react"; | |||
| import SalarySearch from "./SalarySearch"; | |||
| import SalarySearchLoading from "./SalarySearchLoading"; | |||
| import { fetchSalarys } from "@/app/api/salarys"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof SalarySearchLoading; | |||
| @@ -14,7 +15,7 @@ interface SubComponents { | |||
| // } | |||
| const SalarySearchWrapper: React.FC & SubComponents = async () => { | |||
| const Salarys = await fetchSalarys(); | |||
| const [Salarys, abilities] = await Promise.all([fetchSalarys(), getUserAbilities()]); | |||
| // const Salarys:any[] = [] | |||
| const salarysWithHourlyRate = Salarys.map((salary) => { | |||
| // const hourlyRate = calculateHourlyRate(Number(salary.lowerLimit), Number(salary.upperLimit),22, 8) | |||
| @@ -27,7 +28,7 @@ const SalarySearchWrapper: React.FC & SubComponents = async () => { | |||
| }) | |||
| // console.log(salarysWithHourlyRate) | |||
| return <SalarySearch salarys={salarysWithHourlyRate} />; | |||
| return <SalarySearch salarys={salarysWithHourlyRate} abilities={abilities}/>; | |||
| }; | |||
| SalarySearchWrapper.Loading = SalarySearchLoading; | |||
| @@ -9,15 +9,17 @@ import { useRouter } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { SkillResult } from "@/app/api/skill"; | |||
| import { deleteSkill } from "@/app/api/skill/actions"; | |||
| import { MAINTAIN_SKILL } from "@/middleware"; | |||
| interface Props { | |||
| skill: SkillResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<SkillResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const SkillSearch: React.FC<Props> = ({ skill }) => { | |||
| const SkillSearch: React.FC<Props> = ({ skill, abilities }) => { | |||
| const { t } = useTranslation(); | |||
| const [filteredSkill, setFilteredSkill] = useState(skill); | |||
| const router = useRouter(); | |||
| @@ -30,6 +32,8 @@ const SkillSearch: React.FC<Props> = ({ skill }) => { | |||
| const code = t("code") | |||
| const description = t("description") | |||
| const maintainSkill = abilities.includes(MAINTAIN_SKILL) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { | |||
| @@ -73,6 +77,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => { | |||
| label: edit, | |||
| onClick: onSkillClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainSkill, | |||
| }, | |||
| { name: "name", label: name }, | |||
| { name: "code", label: code }, | |||
| @@ -83,6 +88,7 @@ const SkillSearch: React.FC<Props> = ({ skill }) => { | |||
| onClick: deleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error", | |||
| isHidden: !maintainSkill, | |||
| }, | |||
| ], | |||
| [t, onSkillClick, deleteClick] | |||
| @@ -2,15 +2,16 @@ import React from "react"; | |||
| import SkillSearch from "./SkillSearch"; | |||
| import SkillSearchLoading from "./SkillSearchLoading"; | |||
| import { fetchSkill } from "@/app/api/skill"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof SkillSearchLoading; | |||
| } | |||
| 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; | |||
| @@ -10,7 +10,7 @@ import { deleteStaff } from "@/app/api/staff/actions"; | |||
| import { useRouter } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| 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 { Grade } from "@/app/api/grades"; | |||
| import { PositionResult } from "@/app/api/positions"; | |||
| @@ -20,13 +20,13 @@ interface Props { | |||
| teams: TeamResult[] | |||
| grades: Grade[] | |||
| positions: PositionResult[] | |||
| isAuthed: boolean | |||
| abilities: String[] | |||
| } | |||
| type SearchQuery = Partial<Omit<StaffResult, "id">>; | |||
| 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 [filteredStaff, setFilteredStaff] = useState(staff); | |||
| 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 positionCombo = positions.map(p => `${p.name}`) | |||
| const maintainUser = abilities.includes(MAINTAIN_USER) | |||
| const maintainStaff = abilities.includes(MAINTAIN_STAFF) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { | |||
| @@ -104,13 +107,14 @@ const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthe | |||
| label: t("Actions"), | |||
| onClick: onStaffClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainStaff, | |||
| }, | |||
| { | |||
| name: "id", | |||
| label: t("Users"), | |||
| onClick: onUserClick, | |||
| buttonIcon: <Person />, | |||
| isHidden: isAuthed, | |||
| isHidden: !maintainUser, | |||
| }, | |||
| { name: "team", label: t("Team") }, | |||
| { name: "name", label: t("Staff Name") }, | |||
| @@ -123,6 +127,7 @@ const StaffSearch: React.FC<Props> = ({ staff, teams, grades, positions, isAuthe | |||
| onClick: deleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error", | |||
| isHidden: !maintainStaff, | |||
| }, | |||
| ], | |||
| [t, onStaffClick, deleteClick] | |||
| @@ -7,7 +7,7 @@ import { authOptions } from "@/config/authConfig"; | |||
| import { fetchTeam } from "@/app/api/team"; | |||
| import { fetchPositions } from "@/app/api/positions"; | |||
| import { fetchGrades } from "@/app/api/grades"; | |||
| import { MAINTAIN_USER, VIEW_USER } from "@/middleware"; | |||
| import { MAINTAIN_USER } from "@/middleware"; | |||
| interface SubComponents { | |||
| Loading: typeof StaffSearchLoading; | |||
| @@ -19,14 +19,14 @@ interface SessionWithAbilities extends Session { | |||
| const StaffSearchWrapper: React.FC & SubComponents = async () => { | |||
| 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 teams = await fetchTeam(); | |||
| const grades = await fetchGrades(); | |||
| 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; | |||
| @@ -10,18 +10,21 @@ import { useRouter, useSearchParams } from "next/navigation"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { Subsidiary } from "@/app/api/subsidiary"; | |||
| import { deleteSubsidiary } from "@/app/api/subsidiary/actions"; | |||
| import { MAINTAIN_SUBSIDIARY } from "@/middleware"; | |||
| interface Props { | |||
| subsidiaries: Subsidiary[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<Subsidiary, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => { | |||
| const SubsidiarySearch: React.FC<Props> = ({ subsidiaries, abilities }) => { | |||
| const { t } = useTranslation(); | |||
| const router = useRouter() | |||
| const searchParams = useSearchParams() | |||
| const maintainSubsidiary = abilities.includes(MAINTAIN_SUBSIDIARY) | |||
| const [filteredSubsidiaries, setFilteredSubsidiaries] = useState(subsidiaries); | |||
| useEffect(() => { | |||
| @@ -63,6 +66,7 @@ const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => { | |||
| label: t("Details"), | |||
| onClick: onTaskClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainSubsidiary, | |||
| }, | |||
| { name: "code", label: t("Subsidiary Code") }, | |||
| { name: "name", label: t("Subsidiary Name") }, | |||
| @@ -71,7 +75,8 @@ const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => { | |||
| label: t("Delete"), | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainSubsidiary, | |||
| }, | |||
| ], | |||
| [onTaskClick, t], | |||
| @@ -2,15 +2,16 @@ import React from "react"; | |||
| import SubsidiarySearch from "./SubsidiarySearch"; | |||
| import SubsidiarySearchLoading from "./SubsidiarySearchLoading"; | |||
| import { fetchAllSubsidiaries } from "@/app/api/subsidiary"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| interface SubComponents { | |||
| Loading: typeof SubsidiarySearchLoading; | |||
| } | |||
| 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; | |||
| @@ -10,14 +10,16 @@ import DeleteIcon from "@mui/icons-material/Delete"; | |||
| import { useRouter } from "next/navigation"; | |||
| import { deleteTeam } from "@/app/api/team/actions"; | |||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||
| import { MAINTAIN_TEAM } from "@/middleware"; | |||
| interface Props { | |||
| team: TeamResult[]; | |||
| abilities: String[]; | |||
| } | |||
| type SearchQuery = Partial<Omit<TeamResult, "id">>; | |||
| type SearchParamNames = keyof SearchQuery; | |||
| const TeamSearch: React.FC<Props> = ({ team }) => { | |||
| const TeamSearch: React.FC<Props> = ({ team, abilities }) => { | |||
| const { t } = useTranslation(); | |||
| const [filteredTeam, setFilteredTeam] = useState(team); | |||
| const router = useRouter(); | |||
| @@ -29,6 +31,8 @@ const TeamSearch: React.FC<Props> = ({ team }) => { | |||
| const teamLead = t("teamLead") | |||
| const delete_t = t("delete") | |||
| const maintainTeam = abilities.includes(MAINTAIN_TEAM) | |||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||
| () => [ | |||
| { | |||
| @@ -77,6 +81,7 @@ const TeamSearch: React.FC<Props> = ({ team }) => { | |||
| label: edit, | |||
| onClick: onTeamClick, | |||
| buttonIcon: <EditNote />, | |||
| isHidden: !maintainTeam, | |||
| }, | |||
| { name: "name", label: name }, | |||
| { name: "code", label: code }, | |||
| @@ -87,7 +92,8 @@ const TeamSearch: React.FC<Props> = ({ team }) => { | |||
| label: delete_t, | |||
| onClick: onDeleteClick, | |||
| buttonIcon: <DeleteIcon />, | |||
| color: "error" | |||
| color: "error", | |||
| isHidden: !maintainTeam, | |||
| }, | |||
| ], | |||
| [t] | |||
| @@ -3,6 +3,7 @@ import React from "react"; | |||
| import TeamSearch from "./TeamSearch"; | |||
| import TeamSearchLoading from "./TeamSearchLoading"; | |||
| import { fetchTeam } from "@/app/api/team"; | |||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | |||
| // import { preloadTeam } from "@/app/api/Team"; | |||
| interface SubComponents { | |||
| @@ -10,10 +11,10 @@ interface SubComponents { | |||
| } | |||
| const TeamSearchWrapper: React.FC & SubComponents = async () => { | |||
| const Team = await fetchTeam(); | |||
| const [Team, abilities] = await Promise.all([fetchTeam(), getUserAbilities()]); | |||
| console.log(Team); | |||
| return <TeamSearch team={Team} />; | |||
| return <TeamSearch team={Team} abilities={abilities}/>; | |||
| }; | |||
| TeamSearchWrapper.Loading = TeamSearchLoading; | |||
| @@ -11,22 +11,41 @@ export const [ | |||
| NORMAL_STAFF, | |||
| SUPPORTING_STAFF | |||
| ] = [ | |||
| "Super Admin", | |||
| "Top Management", | |||
| "Team Leader", | |||
| "Normal Staff", | |||
| "Supporting Staff" | |||
| ] | |||
| "Super Admin", | |||
| "Top Management", | |||
| "Team Leader", | |||
| "Normal Staff", | |||
| "Supporting Staff" | |||
| ] | |||
| // abilities | |||
| export const [ | |||
| VIEW_USER, | |||
| MAINTAIN_USER, | |||
| MAINTAIN_TIMESHEET, | |||
| VIEW_TASK_TEMPLATE, | |||
| 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_ALL, | |||
| IMPORT_INVOICE, | |||
| @@ -41,27 +60,46 @@ export const [ | |||
| DELETE_PROJECT, | |||
| 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 = [ | |||
| "/analytics", | |||
| @@ -93,7 +131,7 @@ export default async function middleware( | |||
| const authMiddleware = withAuth({ | |||
| pages: authOptions.pages, | |||
| callbacks: { | |||
| authorized: ({req, token}) => { | |||
| authorized: ({ req, token }) => { | |||
| let isAuth = Boolean(token); | |||
| if (!Boolean(token)) { | |||
| return Boolean(token) | |||
| @@ -109,26 +147,96 @@ export default async function middleware( | |||
| } | |||
| 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')) { | |||
| 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')) { | |||
| isAuth = [MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)); | |||
| isAuth = [MAINTAIN_USER].some((ability) => abilities.includes(ability)); | |||
| } | |||
| if (req.nextUrl.pathname.startsWith('/analytics')) { | |||
| isAuth = [GENERATE_REPORTS].some((ability) => abilities.includes(ability)); | |||
| } | |||
| if (req.nextUrl.pathname.startsWith('/settings/staff/edit')) { | |||
| isAuth = [VIEW_STAFF_PROFILE].some((ability) => abilities.includes(ability)); | |||
| } | |||
| if (req.nextUrl.pathname.startsWith('/invoice')) { | |||
| isAuth = [IMPORT_INVOICE, IMPORT_RECEIPT].some((ability) => abilities.includes(ability)); | |||
| } | |||
| if (req.nextUrl.pathname.startsWith('/dashboard')) { | |||
| isAuth = [VIEW_DASHBOARD_ALL, VIEW_DASHBOARD_SELF].some((ability) => abilities.includes(ability)); | |||
| } | |||
| return isAuth | |||
| } | |||
| } | |||