| @@ -0,0 +1,50 @@ | |||||
| import SalarySearch from "@/components/SalarySearch"; | |||||
| import { Metadata } from "next"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import Add from "@mui/icons-material/Add"; | |||||
| import Button from "@mui/material/Button"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import Link from "next/link"; | |||||
| import { Suspense } from "react"; | |||||
| import { fetchSalarys, preloadSalarys } from "@/app/api/salarys"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Salary", | |||||
| }; | |||||
| const Salary: React.FC = async () => { | |||||
| const { t } = await getServerI18n("Salary"); | |||||
| // Preload necessary dependencies | |||||
| // fetchSalarys(); | |||||
| // preloadSalarys(); | |||||
| return ( | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("Salary")} | |||||
| </Typography> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| LinkComponent={Link} | |||||
| href="/settings/position/new" | |||||
| > | |||||
| {t("Create Salary")} | |||||
| </Button> | |||||
| </Stack> | |||||
| <Suspense fallback={<SalarySearch.Loading />}> | |||||
| <SalarySearch/> | |||||
| </Suspense> | |||||
| </> | |||||
| ) | |||||
| }; | |||||
| export default Salary; | |||||
| @@ -0,0 +1,22 @@ | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { cache } from "react"; | |||||
| import "server-only"; | |||||
| export interface SalaryResult { | |||||
| id: number; | |||||
| lowerLimit: number; | |||||
| upperLimit: number; | |||||
| salaryPoint: number; | |||||
| salary: number; | |||||
| } | |||||
| export const preloadSalarys = () => { | |||||
| fetchSalarys(); | |||||
| }; | |||||
| export const fetchSalarys = cache(async () => { | |||||
| return serverFetchJson<SalaryResult[]>(`${BASE_API_URL}/salarys`, { | |||||
| next: { tags: ["salarys"] }, | |||||
| }); | |||||
| }); | |||||
| @@ -22,6 +22,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| "/settings/department/new": "Create Department", | "/settings/department/new": "Create Department", | ||||
| "/settings/position": "Position", | "/settings/position": "Position", | ||||
| "/settings/position/new": "Create Position", | "/settings/position/new": "Create Position", | ||||
| "/settings/salarys": "Salary", | |||||
| }; | }; | ||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| @@ -21,6 +21,7 @@ import Staff from "@mui/icons-material/PeopleAlt"; | |||||
| import Company from '@mui/icons-material/Store'; | import Company from '@mui/icons-material/Store'; | ||||
| import Department from '@mui/icons-material/Diversity3'; | import Department from '@mui/icons-material/Diversity3'; | ||||
| import Position from '@mui/icons-material/Paragliding'; | import Position from '@mui/icons-material/Paragliding'; | ||||
| import Salary from '@mui/icons-material/AttachMoney'; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||
| @@ -98,6 +99,7 @@ const navigationItems: NavigationItem[] = [ | |||||
| { icon: <Company />, label: "Company", path: "/settings/company" }, | { icon: <Company />, label: "Company", path: "/settings/company" }, | ||||
| { icon: <Department />, label: "Department", path: "/settings/department" }, | { icon: <Department />, label: "Department", path: "/settings/department" }, | ||||
| { icon: <Position />, label: "Position", path: "/settings/position" }, | { icon: <Position />, label: "Position", path: "/settings/position" }, | ||||
| { icon: <Salary />, label: "Salary", path: "/settings/salary" }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| @@ -0,0 +1,77 @@ | |||||
| "use client"; | |||||
| import React, { useCallback, useMemo, useState } from "react"; | |||||
| import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| import SearchResults, { Column } from "../SearchResults"; | |||||
| import EditNote from "@mui/icons-material/EditNote"; | |||||
| import { SalaryResult } from "@/app/api/salarys"; | |||||
| interface Props { | |||||
| salarys: SalaryResult[]; | |||||
| } | |||||
| type SearchQuery = Partial<Omit<SalaryResult, "id">>; | |||||
| type SearchParamNames = keyof SearchQuery; | |||||
| const SalarySearch: React.FC<Props> = ({ salarys }) => { | |||||
| const { t } = useTranslation("salarys"); | |||||
| const [filteredSalarys, setFilteredSalarys] = useState(salarys); | |||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | |||||
| () => [ | |||||
| { label: t("Salary"), paramName: "salary", type: "text" }, | |||||
| { label: t("Salary Point"), paramName: "salaryPoint", type: "text" }, | |||||
| ], | |||||
| [t, salarys], | |||||
| ); | |||||
| const onReset = useCallback(() => { | |||||
| setFilteredSalarys(salarys); | |||||
| }, [salarys]); | |||||
| const onSalaryClick = useCallback((project: SalaryResult) => { | |||||
| console.log(project); | |||||
| }, []); | |||||
| const columns = useMemo<Column<SalaryResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "id", | |||||
| label: t("Details"), | |||||
| onClick: onSalaryClick, | |||||
| buttonIcon: <EditNote />, | |||||
| }, | |||||
| { name: "salaryPoint", label: t("Salary Point") }, | |||||
| { name: "lowerLimit", label: t("Lower Limit") }, | |||||
| { name: "upperLimit", label: t("Upper Limit") }, | |||||
| ], | |||||
| [t, onSalaryClick], | |||||
| ); | |||||
| return ( | |||||
| <> | |||||
| <SearchBox | |||||
| criteria={searchCriteria} | |||||
| onSearch={(query) => { | |||||
| setFilteredSalarys( | |||||
| salarys.filter( | |||||
| (s) => | |||||
| ((s.lowerLimit <= Number(query.salary))&& | |||||
| (s.upperLimit >= Number(query.salary)))|| | |||||
| (s.salaryPoint === Number(query.salaryPoint)) | |||||
| ), | |||||
| ); | |||||
| }} | |||||
| onReset={onReset} | |||||
| /> | |||||
| <SearchResults<SalaryResult> | |||||
| items={filteredSalarys} | |||||
| columns={columns} | |||||
| /> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default SalarySearch; | |||||
| @@ -0,0 +1,40 @@ | |||||
| import Card from "@mui/material/Card"; | |||||
| import CardContent from "@mui/material/CardContent"; | |||||
| import Skeleton from "@mui/material/Skeleton"; | |||||
| import Stack from "@mui/material/Stack"; | |||||
| import React from "react"; | |||||
| // Can make this nicer | |||||
| export const SalarySearchLoading: React.FC = () => { | |||||
| return ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton variant="rounded" height={60} /> | |||||
| <Skeleton | |||||
| variant="rounded" | |||||
| height={50} | |||||
| width={100} | |||||
| sx={{ alignSelf: "flex-end" }} | |||||
| /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <Card>Salary | |||||
| <CardContent> | |||||
| <Stack spacing={2}> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| <Skeleton variant="rounded" height={40} /> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default SalarySearchLoading; | |||||
| @@ -0,0 +1,20 @@ | |||||
| // import { fetchSalaryCategories, fetchSalarys } from "@/app/api/companys"; | |||||
| import React from "react"; | |||||
| import SalarySearch from "./SalarySearch"; | |||||
| import SalarySearchLoading from "./SalarySearchLoading"; | |||||
| import { fetchSalarys } from "@/app/api/salarys"; | |||||
| interface SubComponents { | |||||
| Loading: typeof SalarySearchLoading; | |||||
| } | |||||
| const SalarySearchWrapper: React.FC & SubComponents = async () => { | |||||
| const Salarys = await fetchSalarys(); | |||||
| // const Salarys:any[] = [] | |||||
| return <SalarySearch salarys={Salarys} />; | |||||
| }; | |||||
| SalarySearchWrapper.Loading = SalarySearchLoading; | |||||
| export default SalarySearchWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./SalarySearchWrapper"; | |||||