| @@ -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", | |||
| @@ -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", | |||
| @@ -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", | |||
| @@ -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 } = { | |||
| "": "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 ( | |||
| @@ -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"; | |||
| @@ -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"; | |||
| @@ -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 ( | |||
| <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"; | |||
| 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<Props> = ({ 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 ( | |||
| <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 | |||
| 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> | |||
| </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"; | |||