| @@ -33,6 +33,7 @@ | |||||
| "react-hook-form": "^7.49.2", | "react-hook-form": "^7.49.2", | ||||
| "react-i18next": "^13.5.0", | "react-i18next": "^13.5.0", | ||||
| "react-intl": "^6.5.5", | "react-intl": "^6.5.5", | ||||
| "react-number-format": "^5.3.4", | |||||
| "react-select": "^5.8.0", | "react-select": "^5.8.0", | ||||
| "reactstrap": "^9.2.2", | "reactstrap": "^9.2.2", | ||||
| "styled-components": "^6.1.8", | "styled-components": "^6.1.8", | ||||
| @@ -8031,6 +8032,18 @@ | |||||
| "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | ||||
| "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" | "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": { | "node_modules/react-popper": { | ||||
| "version": "2.3.0", | "version": "2.3.0", | ||||
| "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", | "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", | ||||
| @@ -34,6 +34,7 @@ | |||||
| "react-hook-form": "^7.49.2", | "react-hook-form": "^7.49.2", | ||||
| "react-i18next": "^13.5.0", | "react-i18next": "^13.5.0", | ||||
| "react-intl": "^6.5.5", | "react-intl": "^6.5.5", | ||||
| "react-number-format": "^5.3.4", | |||||
| "react-select": "^5.8.0", | "react-select": "^5.8.0", | ||||
| "reactstrap": "^9.2.2", | "reactstrap": "^9.2.2", | ||||
| "styled-components": "^6.1.8", | "styled-components": "^6.1.8", | ||||
| @@ -1,6 +1,6 @@ | |||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| import UserWorkspacePage from "@/components/UserWorkspacePage/UserWorkspacePage"; | |||||
| import UserWorkspacePage from "@/components/UserWorkspacePage"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "User Workspace", | title: "User Workspace", | ||||
| @@ -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<Theme>; | |||||
| 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 ( | |||||
| <div | |||||
| role="tabpanel" | |||||
| hidden={value !== index} | |||||
| id={`simple-tabpanel-${index}`} | |||||
| aria-labelledby={`simple-tab-${index}`} | |||||
| {...other} | |||||
| > | |||||
| {value === index && ( | |||||
| <Box sx={{ p: 3 }}> | |||||
| <Typography>{children}</Typography> | |||||
| </Box> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| function a11yProps(index: number) { | |||||
| return { | |||||
| id: `simple-tab-${index}`, | |||||
| "aria-controls": `simple-tabpanel-${index}`, | |||||
| }; | |||||
| } | |||||
| const AssignedProjectGrid: React.FC<AssignedProjectGridProps> = ({ | |||||
| 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 ( | |||||
| <div style={{ height: height ?? 400, width: "100%" }}> | |||||
| <Card style={{ margin: "auto 20px auto 20px" }}> | |||||
| {Title && <CardHeader title={Title} />} | |||||
| <CardContent | |||||
| style={{ | |||||
| padding: "0px 24px 24px 24px", | |||||
| display: "flex", | |||||
| alignItems: "center", | |||||
| }} | |||||
| > | |||||
| <div> | |||||
| <ThemeProvider theme={TAB_THEME}> | |||||
| <Box sx={{ borderBottom: 4, borderColor: "divider" }}> | |||||
| <Tabs | |||||
| value={value} | |||||
| onChange={handleChange} | |||||
| aria-label="Manage assigned project" | |||||
| > | |||||
| <Tab label="All Projects" {...a11yProps(0)} /> | |||||
| <Tab label="On Track" {...a11yProps(1)} /> | |||||
| <Tab label="Potential Delay" {...a11yProps(2)} /> | |||||
| </Tabs> | |||||
| </Box> | |||||
| {/* <CustomTabPanel value={value} index={0}> | |||||
| Item {value} | |||||
| </CustomTabPanel> | |||||
| <CustomTabPanel value={value} index={1}> | |||||
| Item {value} | |||||
| </CustomTabPanel> | |||||
| <CustomTabPanel value={value} index={2}> | |||||
| Item {value} | |||||
| </CustomTabPanel> */} | |||||
| </ThemeProvider> | |||||
| </div> | |||||
| </CardContent> | |||||
| <AllProjectGrid tab={value} /> | |||||
| </Card> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default AssignedProjectGrid; | |||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./AssignedProjectGrid"; | |||||
| @@ -9,6 +9,7 @@ import { useTranslation } from "react-i18next"; | |||||
| const pathToLabelMap: { [path: string]: string } = { | const pathToLabelMap: { [path: string]: string } = { | ||||
| "": "Overview", | "": "Overview", | ||||
| "/home": "User Workspace", | |||||
| "/projects": "Projects", | "/projects": "Projects", | ||||
| "/projects/create": "Create Project", | "/projects/create": "Create Project", | ||||
| "/tasks": "Task Template", | "/tasks": "Task Template", | ||||
| @@ -26,7 +27,7 @@ const pathToLabelMap: { [path: string]: string } = { | |||||
| const Breadcrumb = () => { | const Breadcrumb = () => { | ||||
| const pathname = usePathname(); | const pathname = usePathname(); | ||||
| const segments = pathname.split("/"); | const segments = pathname.split("/"); | ||||
| // const { t } = useTranslation("customer"); | // const { t } = useTranslation("customer"); | ||||
| return ( | return ( | ||||
| @@ -1,25 +1,9 @@ | |||||
| "use client"; | "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 { 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 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 TimesheetInputGrid from "./LeaveInputGrid"; | ||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| // import { fetchLeaves } from "@/app/api/leave"; | // import { fetchLeaves } from "@/app/api/leave"; | ||||
| @@ -1,25 +1,9 @@ | |||||
| "use client"; | "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 { 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 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 TimesheetInputGrid from "./TimesheetInputGrid"; | ||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| // import { fetchTimesheets } from "@/app/api/timesheets"; | // import { fetchTimesheets } from "@/app/api/timesheets"; | ||||
| @@ -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<UserWorkspaceProps> = ({ 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<HTMLInputElement> | |||||
| >((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 ( | |||||
| <> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Stack gap={2}> | |||||
| <Typography variant="overline" display="block"> | |||||
| {t("Assigned Projects")} | |||||
| </Typography> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6} display="flex" alignItems="center"> | |||||
| <Search sx={{ marginInlineEnd: 1 }} /> | |||||
| <TextField | |||||
| variant="standard" | |||||
| fullWidth | |||||
| onChange={onQueryInputChange} | |||||
| value={query} | |||||
| placeholder={t("Search projects by name or code")} | |||||
| InputProps={{ | |||||
| endAdornment: query && ( | |||||
| <InputAdornment position="end"> | |||||
| <IconButton onClick={clearQueryInput}> | |||||
| <Clear /> | |||||
| </IconButton> | |||||
| </InputAdornment> | |||||
| ), | |||||
| }} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={3}> | |||||
| <FormControl fullWidth> | |||||
| <InputLabel size="small">{t("Project Status")}</InputLabel> | |||||
| <Select | |||||
| label={t("Project Status")} | |||||
| size="small" | |||||
| value={statusFilter} | |||||
| onChange={onStatusChange} | |||||
| > | |||||
| {allStatuses.map((option, index) => ( | |||||
| <MenuItem key={`${option}-${index}`} value={option}> | |||||
| {option} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| </FormControl> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| <ProjectGrid projects={filteredProjects} /> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default AssignedProjects; | |||||
| @@ -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<ProjectGridProps> = (props) => { | |||||
| const [items, setItems] = React.useState<typeof cards>([]); | |||||
| 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<string, string>) => { | |||||
| return ( | |||||
| <Card style={PROJECT_CARD_STYLE}> | |||||
| <CardHeader | |||||
| style={{ backgroundColor: "pink" }} | |||||
| title={item.code + "\u000A" + item.name} | |||||
| /> | |||||
| <CardContent> | |||||
| <p>Hours Spent: {item.hr_spent}</p> | |||||
| <p>Normal (Others): {item.hr_spent_normal}</p> | |||||
| <p>Hours Allocated: {item.hr_alloc}</p> | |||||
| <p>Normal (Others): {item.hr_alloc_normal}</p> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| // Apply the preset style to the cards in child, if not specified // | |||||
| const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
| const { t } = useTranslation("home"); | |||||
| return ( | return ( | ||||
| <Grid container md={12}> | |||||
| {/* <CustomSearchForm applySearch={applySearch} fields={InputFields}/> */} | |||||
| {/* item count = {items?.length??"idk"} , track/tab = {props.tab} */} | |||||
| <CustomCardGrid | |||||
| Title={props.tab.toString()} | |||||
| items={items} | |||||
| cardStyle={cardLayout} | |||||
| /> | |||||
| {/* <CustomCardGrid Title={props.tab.toString()} rows={rows} columns={columns} columnWidth={200} items={items}/> */} | |||||
| </Grid> | |||||
| <Box> | |||||
| <Grid container columns={{ xs: 4, sm: 8, md: 12, lg: 16 }} spacing={2}> | |||||
| {projects.map((project, idx) => ( | |||||
| <Grid key={`${project.code}${idx}`} item xs={4}> | |||||
| <Card> | |||||
| <CardContent> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "flex-end", | |||||
| marginBlockEnd: 1, | |||||
| }} | |||||
| > | |||||
| <Chip | |||||
| size="small" | |||||
| label={project.projectStatus} | |||||
| color={ | |||||
| project.projectStatus === "On Track" | |||||
| ? "success" | |||||
| : "warning" | |||||
| } | |||||
| /> | |||||
| </Box> | |||||
| <Typography variant="overline">{project.code}</Typography> | |||||
| <Typography | |||||
| variant="h6" | |||||
| sx={{ | |||||
| overflow: "hidden", | |||||
| textOverflow: "ellipsis", | |||||
| whiteSpace: "nowrap", | |||||
| marginBlockEnd: 3, | |||||
| }} | |||||
| > | |||||
| {project.name} | |||||
| </Typography> | |||||
| {/* Hours Spent */} | |||||
| <Typography variant="subtitle2">{t("Hours Spent:")}</Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| alignItems: "baseline", | |||||
| }} | |||||
| > | |||||
| <Typography variant="caption">{t("Normal")}</Typography> | |||||
| <Typography> | |||||
| {manhourFormatter.format(project.hoursSpent)} | |||||
| </Typography> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| alignItems: "baseline", | |||||
| }} | |||||
| > | |||||
| <Typography variant="caption">{t("(Others)")}</Typography> | |||||
| <Typography>{`(${manhourFormatter.format( | |||||
| project.hoursSpentOther, | |||||
| )})`}</Typography> | |||||
| </Box> | |||||
| {/* Hours Allocated */} | |||||
| <Typography variant="subtitle2" sx={{ marginBlockStart: 2 }}> | |||||
| {t("Hours Allocated:")} | |||||
| </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| alignItems: "baseline", | |||||
| }} | |||||
| > | |||||
| <Typography variant="caption">{t("Normal")}</Typography> | |||||
| <Typography> | |||||
| {manhourFormatter.format(project.hoursAllocated)} | |||||
| </Typography> | |||||
| </Box> | |||||
| <Box | |||||
| sx={{ | |||||
| display: "flex", | |||||
| justifyContent: "space-between", | |||||
| alignItems: "baseline", | |||||
| }} | |||||
| > | |||||
| <Typography variant="caption">{t("(Others)")}</Typography> | |||||
| <Typography>{`(${manhourFormatter.format( | |||||
| project.hoursAllocatedOther, | |||||
| )})`}</Typography> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| </Grid> | |||||
| ))} | |||||
| </Grid> | |||||
| </Box> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,80 +1,79 @@ | |||||
| "use client"; | "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 { 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 Button from "@mui/material/Button"; | ||||
| import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
| import { Add } from "@mui/icons-material"; | 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 EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal"; | ||||
| import EnterLeaveModal from "../EnterLeave/EnterLeaveModal"; | 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<Props> = ({ allProjects }) => { | |||||
| const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | ||||
| const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | ||||
| const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
| const handleAddTimesheetButtonClick = () => { | |||||
| const handleAddTimesheetButtonClick = useCallback(() => { | |||||
| setTimeheetModalVisible(true); | setTimeheetModalVisible(true); | ||||
| }; | |||||
| }, []); | |||||
| const handleCloseTimesheetModal = () => { | |||||
| const handleCloseTimesheetModal = useCallback(() => { | |||||
| setTimeheetModalVisible(false); | setTimeheetModalVisible(false); | ||||
| }; | |||||
| }, []); | |||||
| const handleAddLeaveButtonClick = () => { | |||||
| const handleAddLeaveButtonClick = useCallback(() => { | |||||
| setLeaveModalVisible(true); | setLeaveModalVisible(true); | ||||
| }; | |||||
| }, []); | |||||
| const handleCloseLeaveModal = () => { | |||||
| const handleCloseLeaveModal = useCallback(() => { | |||||
| setLeaveModalVisible(false); | setLeaveModalVisible(false); | ||||
| }; | |||||
| }, []); | |||||
| return ( | return ( | ||||
| <Grid container height="100vh"> | |||||
| <Grid item sm> | |||||
| <PageTitle BigTitle={"User Workspace"} /> | |||||
| <div> | |||||
| <Stack direction="row" justifyContent="right" flexWrap="wrap"> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| onClick={handleAddTimesheetButtonClick} | |||||
| sx={{ marginRight: "2rem" }} | |||||
| > | |||||
| Enter Timesheet | |||||
| <> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Typography variant="h4" marginInlineEnd={2}> | |||||
| {t("User Workspace")} | |||||
| </Typography> | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="right" | |||||
| flexWrap="wrap" | |||||
| spacing={2} | |||||
| > | |||||
| <ButtonGroup variant="contained"> | |||||
| <Button startIcon={<Add />} onClick={handleAddTimesheetButtonClick}> | |||||
| {t("Enter Time")} | |||||
| </Button> | </Button> | ||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Add />} | |||||
| sx={{ marginRight: "2rem" }} | |||||
| // LinkComponent={Link} | |||||
| // href="/projects/create" | |||||
| onClick={handleAddLeaveButtonClick} | |||||
| > | |||||
| Record Leave | |||||
| <Button startIcon={<Add />} onClick={handleAddLeaveButtonClick}> | |||||
| {t("Record Leave")} | |||||
| </Button> | </Button> | ||||
| </Stack> | |||||
| <Suspense> {/*fallback={<ProjectSearch.Loading />}>*/}</Suspense> | |||||
| </div> | |||||
| <EnterTimesheetModal | |||||
| isOpen={isTimeheetModalVisible} | |||||
| onClose={handleCloseTimesheetModal} | |||||
| /> | |||||
| <EnterLeaveModal | |||||
| isOpen={isLeaveModalVisible} | |||||
| onClose={handleCloseLeaveModal} | |||||
| /> | |||||
| <AssignedProjectGrid Title="Assigned Project" /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </ButtonGroup> | |||||
| </Stack> | |||||
| </Stack> | |||||
| <EnterTimesheetModal | |||||
| isOpen={isTimeheetModalVisible} | |||||
| onClose={handleCloseTimesheetModal} | |||||
| /> | |||||
| <EnterLeaveModal | |||||
| isOpen={isLeaveModalVisible} | |||||
| onClose={handleCloseLeaveModal} | |||||
| /> | |||||
| <AssignedProjects allProjects={allProjects} /> | |||||
| </> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -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 <UserWorkspacePage allProjects={mockProjectCards} />; | |||||
| }; | |||||
| export default UserWorkspaceWrapper; | |||||
| @@ -1 +1 @@ | |||||
| export { default } from "./UserWorkspacePage"; | |||||
| export { default } from "./UserWorkspaceWrapper"; | |||||