diff --git a/package-lock.json b/package-lock.json index b243cb6..84bf9b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-hook-form": "^7.49.2", "react-i18next": "^13.5.0", "react-intl": "^6.5.5", + "react-number-format": "^5.3.4", "react-select": "^5.8.0", "reactstrap": "^9.2.2", "styled-components": "^6.1.8", @@ -8031,6 +8032,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-number-format": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.4.tgz", + "integrity": "sha512-2hHN5mbLuCDUx19bv0Q8wet67QqYK6xmtLQeY5xx+h7UXiMmRtaCwqko4mMPoKXLc6xAzwRrutg8XbTRlsfjRg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-popper": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", diff --git a/package.json b/package.json index c8eea49..5db5867 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-hook-form": "^7.49.2", "react-i18next": "^13.5.0", "react-intl": "^6.5.5", + "react-number-format": "^5.3.4", "react-select": "^5.8.0", "reactstrap": "^9.2.2", "styled-components": "^6.1.8", diff --git a/src/app/(main)/home/page.tsx b/src/app/(main)/home/page.tsx index a710195..176c9a2 100644 --- a/src/app/(main)/home/page.tsx +++ b/src/app/(main)/home/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from "next"; import { I18nProvider } from "@/i18n"; -import UserWorkspacePage from "@/components/UserWorkspacePage/UserWorkspacePage"; +import UserWorkspacePage from "@/components/UserWorkspacePage"; export const metadata: Metadata = { title: "User Workspace", diff --git a/src/components/AssignedProjectGrid/AssignedProjectGrid.tsx b/src/components/AssignedProjectGrid/AssignedProjectGrid.tsx deleted file mode 100644 index a41fc77..0000000 --- a/src/components/AssignedProjectGrid/AssignedProjectGrid.tsx +++ /dev/null @@ -1,247 +0,0 @@ -import * as React from "react"; -import { - Card, - CardHeader, - CardContent, - SxProps, - Theme, - Tabs, - Tab, - Box, - Typography, - Grid, - Link, -} from "@mui/material"; -import { DataGrid, GridColDef } from "@mui/x-data-grid"; -import { darken, lighten, styled } from "@mui/material/styles"; -import { ThemeProvider } from "@emotion/react"; -import { TAB_THEME } from "@/theme/colorConst"; -import AllProjectGrid from "../UserWorkspacePage/ProjectGrid"; - -interface AssignedProjectGridProps { - Title?: string; - // rows: any[]; - // columns: any[]; - columnWidth?: number; - Style?: boolean; - sx?: SxProps; - height?: number; - [key: string]: any; -} - -interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; -} - -function CustomTabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -function a11yProps(index: number) { - return { - id: `simple-tab-${index}`, - "aria-controls": `simple-tabpanel-${index}`, - }; -} - -const AssignedProjectGrid: React.FC = ({ - Title, - rows, - columns, - columnWidth, - Style = true, - sx, - height, - ...props -}) => { - // const modifiedColumns = columns.map((column) => { - // return { - // ...column, - // width: columnWidth ?? 150, - // }; - // }); - - // const rowsWithDefaultValues = rows.map((row) => { - // return { ...row }; - // }); - - const getBackgroundColor = (color: string, mode: "light" | "dark") => - mode === "dark" ? darken(color, 0.7) : lighten(color, 0.7); - - const getHoverBackgroundColor = (color: string, mode: "light" | "dark") => - mode === "dark" ? darken(color, 0.6) : lighten(color, 0.6); - - const getSelectedBackgroundColor = (color: string, mode: "light" | "dark") => - mode === "dark" ? darken(color, 0.5) : lighten(color, 0.5); - - const getSelectedHoverBackgroundColor = ( - color: string, - mode: "light" | "dark", - ) => (mode === "dark" ? darken(color, 0.4) : lighten(color, 0.4)); - - const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ - "& .super-app-theme--Open": { - backgroundColor: getBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), - }, - "&.Mui-selected": { - backgroundColor: getSelectedBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), - }, - }, - }, - "& .super-app-theme--finish": { - backgroundColor: getBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), - }, - "&.Mui-selected": { - backgroundColor: getSelectedBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), - }, - }, - }, - "& .super-app-theme--danger": { - backgroundColor: getBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), - }, - "&.Mui-selected": { - backgroundColor: getSelectedBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), - }, - }, - }, - "& .super-app-theme--warning": { - backgroundColor: getBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), - }, - "&.Mui-selected": { - backgroundColor: getSelectedBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), - "&:hover": { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), - }, - }, - }, - })); - - const [value, setValue] = React.useState(0); - - const handleChange = (event: React.SyntheticEvent, newValue: number) => { - setValue(newValue); - }; - - return ( -
- - {Title && } - -
- - - - - - - - - {/* - Item {value} - - - Item {value} - - - Item {value} - */} - -
-
- -
-
- ); -}; - -export default AssignedProjectGrid; diff --git a/src/components/AssignedProjectGrid/index.ts b/src/components/AssignedProjectGrid/index.ts deleted file mode 100644 index 7192046..0000000 --- a/src/components/AssignedProjectGrid/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./AssignedProjectGrid"; diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index 5b05018..9439cc8 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -9,6 +9,7 @@ import { useTranslation } from "react-i18next"; const pathToLabelMap: { [path: string]: string } = { "": "Overview", + "/home": "User Workspace", "/projects": "Projects", "/projects/create": "Create Project", "/tasks": "Task Template", @@ -26,7 +27,7 @@ const pathToLabelMap: { [path: string]: string } = { const Breadcrumb = () => { const pathname = usePathname(); const segments = pathname.split("/"); - + // const { t } = useTranslation("customer"); return ( diff --git a/src/components/EnterLeave/EnterLeaveModal.tsx b/src/components/EnterLeave/EnterLeaveModal.tsx index a42322c..97f0d3c 100644 --- a/src/components/EnterLeave/EnterLeaveModal.tsx +++ b/src/components/EnterLeave/EnterLeaveModal.tsx @@ -1,25 +1,9 @@ "use client"; -// import { testing } from "@/app/api/timesheets"; -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; -import PageTitle from "../PageTitle/PageTitle"; -import { Suspense } from "react"; import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; -import { Add } from "@mui/icons-material"; -import Link from "next/link"; -import { t } from "i18next"; -import { Card, Modal, Typography } from "@mui/material"; -import CustomModal from "../CustomModal/CustomModal"; -import { PROJECT_MODAL_STYLE } from "@/theme/colorConst"; -import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; -import { DataGrid } from "@mui/x-data-grid"; +import { Card, Modal } from "@mui/material"; import TimesheetInputGrid from "./LeaveInputGrid"; -import { BASE_API_URL } from "@/config/api"; // import { fetchLeaves } from "@/app/api/leave"; diff --git a/src/components/EnterTimesheet/EnterTimesheetModal.tsx b/src/components/EnterTimesheet/EnterTimesheetModal.tsx index c63a905..d8854a1 100644 --- a/src/components/EnterTimesheet/EnterTimesheetModal.tsx +++ b/src/components/EnterTimesheet/EnterTimesheetModal.tsx @@ -1,25 +1,9 @@ "use client"; -// import { testing } from "@/app/api/timesheets"; -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; -import PageTitle from "../PageTitle/PageTitle"; -import { Suspense } from "react"; import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; -import { Add } from "@mui/icons-material"; -import Link from "next/link"; -import { t } from "i18next"; -import { Card, Modal, Typography } from "@mui/material"; -import CustomModal from "../CustomModal/CustomModal"; -import { PROJECT_MODAL_STYLE } from "@/theme/colorConst"; -import CustomDatagrid from "../CustomDatagrid/CustomDatagrid"; -import { DataGrid } from "@mui/x-data-grid"; +import { Card, Modal } from "@mui/material"; import TimesheetInputGrid from "./TimesheetInputGrid"; -import { BASE_API_URL } from "@/config/api"; // import { fetchTimesheets } from "@/app/api/timesheets"; diff --git a/src/components/UserWorkspacePage/AssignedProjects.tsx b/src/components/UserWorkspacePage/AssignedProjects.tsx new file mode 100644 index 0000000..fddcd2e --- /dev/null +++ b/src/components/UserWorkspacePage/AssignedProjects.tsx @@ -0,0 +1,117 @@ +import React, { useEffect, useMemo } from "react"; +import { + Card, + CardContent, + FormControl, + Grid, + IconButton, + InputAdornment, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { Clear, Search } from "@mui/icons-material"; +import ProjectGrid from "./ProjectGrid"; +import { Props as UserWorkspaceProps } from "./UserWorkspacePage"; +import uniq from "lodash/uniq"; + +const AssignedProjects: React.FC = ({ allProjects }) => { + const { t } = useTranslation("home"); + + // Projects + const [filteredProjects, setFilterProjects] = React.useState(allProjects); + + // Query related + const [query, setQuery] = React.useState(""); + const onQueryInputChange = React.useCallback< + React.ChangeEventHandler + >((e) => { + setQuery(e.target.value); + }, []); + const clearQueryInput = React.useCallback(() => { + setQuery(""); + }, []); + + // Filter + const allStatuses = useMemo(() => { + return uniq([ + "All", + ...allProjects.map((project) => project.projectStatus), + ]); + }, [allProjects]); + const [statusFilter, setStatusFilter] = React.useState("All"); + const onStatusChange = React.useCallback((e: SelectChangeEvent) => { + setStatusFilter(e.target.value); + }, []); + + useEffect(() => { + setFilterProjects( + allProjects.filter( + (p) => + (p.code.toLowerCase().includes(query.toLowerCase()) || + p.name.toLowerCase().includes(query.toLowerCase())) && + (p.projectStatus === statusFilter || statusFilter === "All"), + ), + ); + }, [allProjects, query, statusFilter]); + + return ( + <> + + + + + {t("Assigned Projects")} + + + + + + + + + + ), + }} + /> + + + + {t("Project Status")} + + + + + + + + + + ); +}; + +export default AssignedProjects; diff --git a/src/components/UserWorkspacePage/ProjectGrid.tsx b/src/components/UserWorkspacePage/ProjectGrid.tsx index 8cc37d5..51ec16d 100644 --- a/src/components/UserWorkspacePage/ProjectGrid.tsx +++ b/src/components/UserWorkspacePage/ProjectGrid.tsx @@ -1,99 +1,111 @@ -"use client"; -import * as React from "react"; -import Grid from "@mui/material/Grid"; -import { useEffect } from "react"; -import { Card, CardContent, CardHeader } from "@mui/material"; -import CustomCardGrid from "../CustomCardGrid/CustomCardGrid"; -import "../../app/global.css"; -import { PROJECT_CARD_STYLE } from "@/theme/colorConst"; +import React from "react"; +import { ProjectHours } from "./UserWorkspaceWrapper"; +import { Box, Card, CardContent, Chip, Grid, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { manhourFormatter } from "@/app/utils/formatUtil"; -interface ProjectGridProps { - tab: number; +interface Props { + projects: ProjectHours[]; } -const cards = [ - { - code: "M1001 (C)", - name: "Consultancy Project A", - hr_spent: 12.75, - hr_spent_normal: 0.0, - hr_alloc: 150.0, - hr_alloc_normal: 30.0, - }, - { - code: "M1301 (C)", - name: "Consultancy Project AAA", - hr_spent: 4.25, - hr_spent_normal: 0.25, - hr_alloc: 30.0, - hr_alloc_normal: 0.0, - }, - { - code: "M1354 (C)", - name: "Consultancy Project BBB", - hr_spent: 57.0, - hr_spent_normal: 6.5, - hr_alloc: 100.0, - hr_alloc_normal: 20.0, - }, - { - code: "M1973 (C)", - name: "Construction Project CCC", - hr_spent: 12.75, - hr_spent_normal: 0.0, - hr_alloc: 150.0, - hr_alloc_normal: 30.0, - }, - { - code: "M2014 (T)", - name: "Consultancy Project DDD", - hr_spent: 1.0, - hr_spent_normal: 0.0, - hr_alloc: 10.0, - hr_alloc_normal: 0.0, - }, -]; - -const ProjectGrid: React.FC = (props) => { - const [items, setItems] = React.useState([]); - - useEffect(() => { - if (props.tab == 0) { - setItems(cards); - } else { - const filteredItems = cards; //cards.filter(item => (item.track == props.tab)); - setItems(filteredItems); - } - }, [props.tab]); - - const cardLayout = (item: Record) => { - return ( - - - -

Hours Spent: {item.hr_spent}

-

Normal (Others): {item.hr_spent_normal}

-

Hours Allocated: {item.hr_alloc}

-

Normal (Others): {item.hr_alloc_normal}

-
-
- ); - }; - // Apply the preset style to the cards in child, if not specified // +const ProjectGrid: React.FC = ({ projects }) => { + const { t } = useTranslation("home"); return ( - - {/* */} - {/* item count = {items?.length??"idk"} , track/tab = {props.tab} */} - - {/* */} - + + + {projects.map((project, idx) => ( + + + + + + + {project.code} + + {project.name} + + {/* Hours Spent */} + {t("Hours Spent:")} + + {t("Normal")} + + {manhourFormatter.format(project.hoursSpent)} + + + + {t("(Others)")} + {`(${manhourFormatter.format( + project.hoursSpentOther, + )})`} + + {/* Hours Allocated */} + + {t("Hours Allocated:")} + + + {t("Normal")} + + {manhourFormatter.format(project.hoursAllocated)} + + + + {t("(Others)")} + {`(${manhourFormatter.format( + project.hoursAllocatedOther, + )})`} + + + + + ))} + + ); }; diff --git a/src/components/UserWorkspacePage/UserWorkspacePage.tsx b/src/components/UserWorkspacePage/UserWorkspacePage.tsx index 8fdb1cf..a329410 100644 --- a/src/components/UserWorkspacePage/UserWorkspacePage.tsx +++ b/src/components/UserWorkspacePage/UserWorkspacePage.tsx @@ -1,80 +1,79 @@ "use client"; -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; -import { useState } from "react"; + +import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; -import AssignedProjectGrid from "../AssignedProjectGrid/AssignedProjectGrid"; -import PageTitle from "../PageTitle/PageTitle"; -import { Suspense } from "react"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { Add } from "@mui/icons-material"; -import Link from "next/link"; -import { t } from "i18next"; -import { Modal } from "@mui/material"; -import CustomModal from "../CustomModal/CustomModal"; +import { Typography } from "@mui/material"; import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal"; import EnterLeaveModal from "../EnterLeave/EnterLeaveModal"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import AssignedProjects from "./AssignedProjects"; +import { ProjectHours } from "./UserWorkspaceWrapper"; + +export interface Props { + allProjects: ProjectHours[]; +} -const UserWorkspacePage: React.FC = () => { +const UserWorkspacePage: React.FC = ({ allProjects }) => { const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); const { t } = useTranslation("home"); - const handleAddTimesheetButtonClick = () => { + const handleAddTimesheetButtonClick = useCallback(() => { setTimeheetModalVisible(true); - }; + }, []); - const handleCloseTimesheetModal = () => { + const handleCloseTimesheetModal = useCallback(() => { setTimeheetModalVisible(false); - }; + }, []); - const handleAddLeaveButtonClick = () => { + const handleAddLeaveButtonClick = useCallback(() => { setLeaveModalVisible(true); - }; + }, []); - const handleCloseLeaveModal = () => { + const handleCloseLeaveModal = useCallback(() => { setLeaveModalVisible(false); - }; + }, []); return ( - - - -
- - - - - {/*fallback={}>*/} -
- - - -
-
+ + + + + + + ); }; diff --git a/src/components/UserWorkspacePage/UserWorkspaceWrapper.tsx b/src/components/UserWorkspacePage/UserWorkspaceWrapper.tsx new file mode 100644 index 0000000..42b3756 --- /dev/null +++ b/src/components/UserWorkspacePage/UserWorkspaceWrapper.tsx @@ -0,0 +1,65 @@ +import UserWorkspacePage from "./UserWorkspacePage"; + +export interface ProjectHours { + code: string; + name: string; + hoursSpent: number; + hoursSpentOther: number; + hoursAllocated: number; + hoursAllocatedOther: number; + projectStatus: "On Track" | "Potential Delay"; +} + +const mockProjectCards: ProjectHours[] = [ + { + code: "M1001 (C)", + name: "Consultancy Project A", + hoursSpent: 12.75, + hoursSpentOther: 0.0, + hoursAllocated: 150.0, + hoursAllocatedOther: 30.0, + projectStatus: "On Track", + }, + { + code: "M1301 (C)", + name: "Consultancy Project AAA", + hoursSpent: 4.25, + hoursSpentOther: 0.25, + hoursAllocated: 30.0, + hoursAllocatedOther: 0.0, + projectStatus: "On Track", + }, + { + code: "M1354 (C)", + name: "Consultancy Project BBB", + hoursSpent: 57.0, + hoursSpentOther: 6.5, + hoursAllocated: 100.0, + hoursAllocatedOther: 20.0, + projectStatus: "On Track", + }, + { + code: "M1973 (C)", + name: "Construction Project CCC", + hoursSpent: 12.75, + hoursSpentOther: 0.0, + hoursAllocated: 150.0, + hoursAllocatedOther: 30.0, + projectStatus: "Potential Delay", + }, + { + code: "M2014 (T)", + name: "Consultancy Project DDD", + hoursSpent: 1.0, + hoursSpentOther: 0.0, + hoursAllocated: 10.0, + hoursAllocatedOther: 0.0, + projectStatus: "Potential Delay", + }, +]; + +const UserWorkspaceWrapper: React.FC = () => { + return ; +}; + +export default UserWorkspaceWrapper; diff --git a/src/components/UserWorkspacePage/index.ts b/src/components/UserWorkspacePage/index.ts index ec65d8f..21ce7cf 100644 --- a/src/components/UserWorkspacePage/index.ts +++ b/src/components/UserWorkspacePage/index.ts @@ -1 +1 @@ -export { default } from "./UserWorkspacePage"; +export { default } from "./UserWorkspaceWrapper";