@@ -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"; |