Преглед на файлове

WIP

user workspace
enter timesheet input
added Custom Alert for unification
tags/Baseline_30082024_FRONTEND_UAT
kelvinsuen преди 1 година
родител
ревизия
5f820ee5c9
променени са 16 файла, в които са добавени 1591 реда и са изтрити 2 реда
  1. +8
    -2
      src/app/(main)/home/page.tsx
  2. +176
    -0
      src/components/AssignedProjectGrid/AssignedProjectGrid.tsx
  3. +1
    -0
      src/components/AssignedProjectGrid/index.ts
  4. +171
    -0
      src/components/CustomCardGrid/CustomCardGrid.tsx
  5. +1
    -0
      src/components/CustomCardGrid/index.ts
  6. +62
    -0
      src/components/CustomModal/CustomModal.tsx
  7. +1
    -0
      src/components/CustomModal/index.ts
  8. +80
    -0
      src/components/EnterTimesheet/EnterTimesheetModal.tsx
  9. +62
    -0
      src/components/EnterTimesheet/ProjectGrid.tsx
  10. +502
    -0
      src/components/EnterTimesheet/TimesheetInputGrid.tsx
  11. +1
    -0
      src/components/EnterTimesheet/index.ts
  12. +23
    -0
      src/components/Swal/CustomAlerts.js
  13. +62
    -0
      src/components/UserWorkspacePage/ProjectGrid.tsx
  14. +69
    -0
      src/components/UserWorkspacePage/UserWorkspacePage.tsx
  15. +1
    -0
      src/components/UserWorkspacePage/index.ts
  16. +371
    -0
      src/theme/colorConst.js

+ 8
- 2
src/app/(main)/home/page.tsx Целия файл

@@ -1,11 +1,17 @@
import { Metadata } from "next";
import { I18nProvider } from "@/i18n";
import UserWorkspacePage from "@/components/UserWorkspacePage/UserWorkspacePage";

export const metadata: Metadata = {
title: "Home",
title: "User Workspace",
};

const Home: React.FC = async () => {
return "Home";
return (
<I18nProvider namespaces={["home"]}>
<UserWorkspacePage/>
</I18nProvider>
);
};

export default Home;

+ 176
- 0
src/components/AssignedProjectGrid/AssignedProjectGrid.tsx Целия файл

@@ -0,0 +1,176 @@
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
src/components/AssignedProjectGrid/index.ts Целия файл

@@ -0,0 +1 @@
export { default } from "./AssignedProjectGrid";

+ 171
- 0
src/components/CustomCardGrid/CustomCardGrid.tsx Целия файл

@@ -0,0 +1,171 @@
import * as React from 'react';
import { Card, CardHeader, CardContent, SxProps, Theme, Grid } from '@mui/material';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { darken, lighten, styled } from '@mui/material/styles';
import { PROJECT_CARD_STYLE } from '@/theme/colorConst';
import { useRef, useEffect, useState } from 'react';
import Swal from 'sweetalert2';
import styledcmp from 'styled-components';

const CardWrapper = styledcmp.div`
/* Styles for the card when not hovered */
background-color: #f0f0f0;
padding: 10px;
/* ...other styles... */

&:hover {
/* Styles for the card when hovered */
background-color: #c0c0c0;
/* ...other hover styles... */
}
`;

interface CustomCardGridProps {
Title?: string;
cardsPerRow?: number;
rows?: any[];
columns?: any[];
items: any[];
columnWidth?: number;
Style?: boolean;
sx?: SxProps<Theme>;
dataGridHeight?: number;
cardStyle?: any;
[key: string]: any;
}

const CustomCardGrid: React.FC<CustomCardGridProps> = ({
Title,
rows,
items,
columns,
columnWidth,
cardsPerRow = 4,
Style = true,
sx,
dataGridHeight,
...props
}) => {
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 StyledCard = styled(Card)(({ 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 CardItem = (item: any) => {
const cardItem = item.item as Record<string, string>;
return props.cardStyle?? (
// <Grid item sx={{ m: 3 }}>
<StyledCard style={PROJECT_CARD_STYLE}>
<CardContent>
{Object.keys(cardItem).map((key) => (
<p key={key}>
{key}: {cardItem[key]}
</p>
))}
</CardContent>
</StyledCard>
// </Grid>
);
};

const containerRef = useRef<HTMLDivElement>(null!);

const [cardMargin, setCardMargin] = useState(1.5);
useEffect(() => {
console.log(CardItem);
const resizeHandler = () => {
const containerWidth = containerRef.current.offsetWidth;
const cardCount = items.length;
const rootSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
setCardMargin((containerWidth - cardsPerRow * (rootSize * parseInt(PROJECT_CARD_STYLE.width.slice(0, -3),10))) /(2 * cardsPerRow));
// Set the cardMargin value using style={{margin: `${cardMargin}px`, ...PROJECT_CARD_STYLE}}
};
window.addEventListener('resize', resizeHandler);
resizeHandler(); // Initial calculation
// Swal.fire({
// title: 'Error! ',
// text: `Card Count is ${items.length}`,
// icon: 'success',
// confirmButtonText: 'Jus Cool'
// })

return () => {
window.removeEventListener('resize', resizeHandler);
};
}, [items]);

return (
<div ref={containerRef} style={{display:'flex', flexWrap:'wrap', alignItems: 'flex-start'}}>
{/* <p>width is {containerRef.current == null? "idk":containerRef.current.offsetWidth}, margin is {cardMargin}</p> */}
{items.map((item, index) => (
<div key={index}>
{props.cardStyle? props.cardStyle(item) : <CardItem item={item}/>}
</div>
))}
</div>
);
};

export default CustomCardGrid;

+ 1
- 0
src/components/CustomCardGrid/index.ts Целия файл

@@ -0,0 +1 @@
export { default } from "./CustomCardGrid";

+ 62
- 0
src/components/CustomModal/CustomModal.tsx Целия файл

@@ -0,0 +1,62 @@
import * as React from 'react';
import { Card, CardHeader, CardContent, SxProps, Theme, Grid, Modal, Typography, Button } from '@mui/material';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { darken, lighten, styled } from '@mui/material/styles';
import { PROJECT_MODAL_STYLE } from '@/theme/colorConst';
import { useRef, useEffect, useState } from 'react';
import Swal from 'sweetalert2';
import styledcmp from 'styled-components';

const CardWrapper = styledcmp.div`
/* Styles for the card when not hovered */
background-color: #f0f0f0,
padding: 10px,
/* ...other styles... */

&:hover {
/* Styles for the card when hovered */
background-color: #c0c0c0,
/* ...other hover styles... */
}
`;

interface CustomModalProps {
title?: string;
isOpen: boolean;
onClose: () => void;
modalStyle?: any;
}

const CustomModal: React.FC<CustomModalProps> = ({ ...props }) => {

const ModalContent = () => {
return (
// <Grid item sx={{ m: 3 }}>
<div style={PROJECT_MODAL_STYLE}>
<Typography variant="h6" id="modal-title">
{props.title??"Modal Title"}
</Typography>
<Typography variant="h6" id="modal-title" style={{ alignSelf: 'flex-start', margin: '10px' }}>
Modal Content
</Typography>
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<Button variant="contained" onClick={props.onClose}>
Confirm
</Button>
<Button variant="contained" onClick={props.onClose}>
Cancel
</Button>
</div>
</div>
// </Grid>
);
};

return (
<Modal open={props.isOpen} onClose={props.onClose}>
{props.modalStyle? <props.modalStyle props={props}/> : <ModalContent/>}
</Modal>
);
};
export default CustomModal;

+ 1
- 0
src/components/CustomModal/index.ts Целия файл

@@ -0,0 +1 @@
export { default } from "./CustomModal";

+ 80
- 0
src/components/EnterTimesheet/EnterTimesheetModal.tsx Целия файл

@@ -0,0 +1,80 @@
"use client";
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 { 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 TimesheetInputGrid from "./TimesheetInputGrid";

interface EnterTimesheetModalProps {
isOpen: boolean;
onClose: () => void;
modalStyle?: any;
}

const EnterTimesheetModal: React.FC<EnterTimesheetModalProps> = ({ ...props }) => {
const [lockConfirm, setLockConfirm] = useState(false);
const columns = [
{
id: 'projectCode',
field: 'projectCode',
headerName: "Project Code and Name",
flex: 1,
},
{
id: 'task',
field: 'task',
headerName: "Task",
flex: 1,
},
];

const rows = [{
id: 1, projectCode: "M1001", task: "1.2"
},
{
id: 2, projectCode: "M1301", task: "1.1"
}];

return (
<Modal open={props.isOpen} onClose={props.onClose}>
<div style={PROJECT_MODAL_STYLE}>
<Typography variant="h6" id="modal-title" sx={{flex:1}}>
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
Timesheet Input
</div>
</Typography>

<div style={{flex: 10}}>
<TimesheetInputGrid setLockConfirm={setLockConfirm}/>
</div>

<div style={{
display: 'flex', justifyContent: 'space-between', width: '100%', flex: 1
}}>
<Button disabled={lockConfirm} variant="contained" onClick={props.onClose}>
Confirm
</Button>
<Button variant="contained" onClick={props.onClose}
sx={{"background-color":"#F890A5"}}>
Cancel
</Button>
</div>
</div>
</Modal>
);
};

export default EnterTimesheetModal;

+ 62
- 0
src/components/EnterTimesheet/ProjectGrid.tsx Целия файл

@@ -0,0 +1,62 @@
"use client";
import * as React from "react";
import Grid from "@mui/material/Grid";
import { useEffect, useState } from 'react'
import { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import {Card,CardContent,CardHeader} from '@mui/material';
import CustomCardGrid from '../CustomCardGrid/CustomCardGrid';
import '../../app/global.css';
import { PROJECT_CARD_STYLE } from "@/theme/colorConst";

interface ProjectGridProps {
tab: number;
}


const ProjectGrid: React.FC<ProjectGridProps> = (props) => {
const [items, setItems] = React.useState<Object[]>([])
useEffect(() => {
if (props.tab == 0) {
setItems(cards)
}
else {
const filteredItems = cards;//cards.filter(item => (item.track == props.tab));
setItems(filteredItems);
}
}, [props.tab]);
const cards = [
{code: 'M1001 (C)', name: 'Consultancy Project A', hr_spent: 12.75, hr_spent_normal: 0.00, hr_alloc: 150.00, hr_alloc_normal: 30.00},
{code: 'M1301 (C)', name: 'Consultancy Project AAA', hr_spent: 4.25, hr_spent_normal: 0.25, hr_alloc: 30.00, hr_alloc_normal: 0.00},
{code: 'M1354 (C)', name: 'Consultancy Project BBB', hr_spent: 57.00, hr_spent_normal: 6.50, hr_alloc: 100.00, hr_alloc_normal: 20.00},
{code: 'M1973 (C)', name: 'Construction Project CCC', hr_spent: 12.75, hr_spent_normal: 0.00, hr_alloc: 150.00, hr_alloc_normal: 30.00},
{code: 'M2014 (T)', name: 'Consultancy Project DDD', hr_spent: 1.00, hr_spent_normal: 0.00, hr_alloc: 10.00, hr_alloc_normal: 0.00},
];
const cardLayout = (item: Record<string, string>) => {
return (
<Card style={PROJECT_CARD_STYLE}>
<CardHeader style={{backgroundColor:'red'}} 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 //
return (
<Grid container md={12} style={{backgroundColor:"yellow"}}>
{/* <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>
);
};

export default ProjectGrid;

+ 502
- 0
src/components/EnterTimesheet/TimesheetInputGrid.tsx Целия файл

@@ -0,0 +1,502 @@
"use client";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import PageTitle from "../PageTitle/PageTitle";
import { Suspense } from "react";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { Add, SettingsEthernet } from '@mui/icons-material';
import Link from "next/link";
import { t } from 'i18next';
import { Box, Container, Modal, Select, SelectChangeEvent, Typography } from "@mui/material";
import { Close } from '@mui/icons-material';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Swal from "sweetalert2";
import { msg } from "../Swal/CustomAlerts";
import ComboEditor from "../ComboEditor/ComboEditor";
import React from "react";
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import {
GridRowsProp,
GridRowModesModel,
GridRowModes,
DataGrid,
GridColDef,
GridToolbarContainer,
GridFooterContainer,
GridActionsCellItem,
GridEventListener,
GridRowId,
GridRowModel,
GridRowEditStopReasons,
GridEditInputCell,
GridValueSetterParams,
} from '@mui/x-data-grid';
import { LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import { Props } from "react-intl/src/components/relative";

const weekdays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];

interface BottomBarProps {
getHoursTotal: (column: string) => number;
setLockConfirm: (newLock: (oldLock: Boolean) => Boolean) => void;
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
) => void;
}

interface EditToolbarProps {
// setDay: (newDay : dayjs.Dayjs) => void;
setDay: (newDay: (oldDay: dayjs.Dayjs) => dayjs.Dayjs) => void;
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
) => void;
}

interface EditFooterProps {
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
) => void;
}

const BottomBar = (props: BottomBarProps) => {
const { setRows, setRowModesModel, getHoursTotal, setLockConfirm } = props;
// const getHoursTotal = props.getHoursTotal;
const [newId, setNewId] = useState(-1);
const [invalidDays, setInvalidDays] = useState(0);

const handleAddClick = () => {
const id = newId;
setNewId(newId - 1);
setRows((oldRows) => [...oldRows, { id, projectCode: '', task: '', isNew: true }]);
setRowModesModel((oldModel) => ({
...oldModel,
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'projectCode' },
}));
};

const totalColDef = {
flex:1,
// style: {color:getHoursTotal('mon')>24?"red":"black"}
};

const TotalCell = ({value}: Props) => {
const [invalid, setInvalid] = useState(false);

useEffect(()=> {
const newInvalid = (value??0)>24;
setInvalid(newInvalid);
}, [value]);

return (
<Box flex={1} style={{color: invalid?"red":"black"}}>
{value}
</Box>
);
}

const checkUnlockConfirmBtn = () => {
// setLockConfirm((oldLock)=> valid);
setLockConfirm((oldLock)=> weekdays.every(weekday => {
getHoursTotal(weekday) <= 24
}));
}

return (
<div>
<div style={{ display: 'flex', justifyContent: 'flex', width: '100%' }}>
<Box flex={5.7} textAlign={'right'} marginRight='4rem'>
<b>Total:</b>
</Box>
<TotalCell value={getHoursTotal('mon')}/>
<TotalCell value={getHoursTotal('tue')}/>
<TotalCell value={getHoursTotal('wed')}/>
<TotalCell value={getHoursTotal('thu')}/>
<TotalCell value={getHoursTotal('fri')}/>
<TotalCell value={getHoursTotal('sat')}/>
<TotalCell value={getHoursTotal('sun')}/>
</div>
<Button variant="outlined" color="primary" startIcon={<AddIcon />} onClick={handleAddClick}>
Add record
</Button>
</div>
);
}

const EditToolbar = (props: EditToolbarProps) => {
const { setDay } = props;
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs>(dayjs());

const handleClickLeft = () => {
if (selectedDate) {
const newDate = selectedDate.add(-7, 'day');
setSelectedDate(newDate);
}
};
const handleClickRight = () => {
if (selectedDate) {
const newDate = selectedDate.add(7, 'day') > dayjs()? dayjs(): selectedDate.add(7, 'day');
setSelectedDate(newDate);
}
};

const handleDateChange = (date: dayjs.Dayjs | Date | null) => {
const newDate = dayjs(date);
setSelectedDate(newDate);
};

useEffect(() => {
setDay((oldDay) => selectedDate);
}, [selectedDate]);

return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<div style={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
<Button sx={{"border-radius":"30%", marginRight:'20px'}} variant="contained" onClick={handleClickLeft}>
<ArrowBackIcon/>
</Button>
<DatePicker
value={selectedDate}
onChange={handleDateChange}
disableFuture={true}/>
<Button sx={{"border-radius":"30%", margin:'0px 20px 0px 20px'}} variant="contained" onClick={handleClickRight}>
<ArrowForwardIcon/>
</Button>
</div>
</LocalizationProvider>
);
}

const EditFooter = (props: EditFooterProps) => {
return (
<div style={{ display: 'flex', justifyContent: 'flex', width: '100%' }}>
<Box flex={1}>
<b>Total: </b>
</Box>
<Box flex={2}>ssss</Box>
</div>
);
}

interface TimesheetInputGridProps {
setLockConfirm: (newLock: (oldLock: Boolean) => Boolean) => void;
onClose?: () => void;
}

const initialRows: GridRowsProp = [
{
id: 1,
projectCode: "M1001",
task: "1.2",
mon: 2.5,
},
{
id: 2,
projectCode: "M1002",
task: "1.3",
mon: 3.25,
},
];

const options=["M1001", "M1301", "M1354", "M1973"];
const options2=[
"1.1 - Preparation of preliminary Cost Estimate / Cost Plan",
"1.2 - Cash flow forecast",
"1.3 - Cost studies fo alterative design solutions",
"1.4 = Attend design co-ordination / project review meetings",
"1.5 - Prepare / Review RIC"];

const getDateForHeader = (date : dayjs.Dayjs, weekday : number) => {
if (date.day() == 0) {
return date.add((weekday - date.day() - 7), 'day').format('DD MMM');
} else {
return date.add(weekday - date.day(), 'day').format('DD MMM');
}
}

const TimesheetInputGrid: React.FC<TimesheetInputGridProps> = ({ ...props }) => {

const [rows, setRows] = useState(initialRows);
const [day, setDay] = useState(dayjs());
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>({});
const { setLockConfirm } = props;

const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
if (params.reason === GridRowEditStopReasons.rowFocusOut) {
event.defaultMuiPrevented = true;
}
};

const handleEditClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};

const handleSaveClick = (id: GridRowId) => () => {

setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};

const handleDeleteClick = (id: GridRowId) => () => {
setRows(rows.filter((row) => row.id !== id));
};

const handleCancelClick = (id: GridRowId) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true },
});

const editedRow = rows.find((row) => row.id === id);
if (editedRow!.isNew) {
setRows(rows.filter((row) => row.id !== id));
}
};

const processRowUpdate = (newRow: GridRowModel) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
return updatedRow;
};

const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
setRowModesModel(newRowModesModel);
};

const getHoursTotal = (column : any) => {
let sum = 0;
rows.forEach((row) => {
sum += row[column]??0;
});
return sum;
};

const weekdayColConfig : any = {
type: 'number',
// sortable: false,
//width: 100,
flex: 1,
align: 'left',
headerAlign: 'left',
editable: true,
renderEditCell: (value : any) => (
<GridEditInputCell
{...value}
inputProps={{
max: 24,
min: 0,
step: 0.25,
}}
/>
),
};

const columns: GridColDef[] = [
{
field: 'actions',
type: 'actions',
headerName: '',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
title="Save"
label="Save"
sx={{
color: 'primary.main',
}}
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
title="Cancel"
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>,
];
}

return [
<GridActionsCellItem
icon={<EditIcon />}
title="Edit"
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem
title="Delete"
label="Delete"
icon={<DeleteIcon />}
onClick={handleDeleteClick(id)}
sx={{color:"red"}}
/>,
];
},
},
{
field: 'projectCode',
headerName: 'Project Code',
// width: 220,
flex: 2,
editable: true,
type: 'singleSelect',
valueOptions: options,
},
{
field: 'task',
headerName: 'Task',
// width: 220,
flex: 3,
editable: true,
type: 'singleSelect',
valueOptions: options2,
},
{
// Mon
field: 'mon',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Mon - {getDateForHeader(day, 1)}</div>
);
},
},
{
// Tue
field: 'tue',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Tue - {getDateForHeader(day, 2)}</div>
);
},
},
{
// Wed
field: 'wed',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Wed - {getDateForHeader(day, 3)}</div>
);
},
},
{
// Thu
field: 'thu',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Thu - {getDateForHeader(day, 4)}</div>
);
},
},
{
// Fri
field: 'fri',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Fri - {getDateForHeader(day, 5)}</div>
);
},
},
{
// Sat
field: 'sat',
...weekdayColConfig,
renderHeader: () => {
return (
<div>Sat - {getDateForHeader(day, 6)}</div>
);
},
},
{
// Sun
field: 'sun',
...weekdayColConfig,
renderHeader: () => {
return (
<div style={{color:"red"}}>Sun - {getDateForHeader(day, 7)}</div>
);
},
},
// {
// field: 'joinDate',
// headerName: 'Join date',
// type: 'date',
// width: 180,
// editable: true,
// },
];

return (
<Box
sx={{
// marginBottom: '-5px',
height: '30rem',
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .header': {
// border: 1,
// 'border-width': '1px',
// 'border-color': 'grey',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<DataGrid
rows={rows}
columns={columns}
editMode="row"
rowModesModel={rowModesModel}
onRowModesModelChange={handleRowModesModelChange}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
disableRowSelectionOnClick={true}
disableColumnMenu={true}
hideFooterPagination={true}
slots={{
toolbar: EditToolbar,
// footer: EditFooter,
}}
slotProps={{
toolbar: { setDay, setRows, setRowModesModel },
// footer: { setDay, setRows, setRowModesModel },
}}
initialState={{
pagination: { paginationModel: { pageSize: 100 } },
}}
/>

<BottomBar getHoursTotal={getHoursTotal} setRows={setRows} setRowModesModel={setRowModesModel} setLockConfirm={setLockConfirm}/>
</Box>
);
}

export default TimesheetInputGrid;

+ 1
- 0
src/components/EnterTimesheet/index.ts Целия файл

@@ -0,0 +1 @@
export { default } from "./EnterTimesheetModal";

+ 23
- 0
src/components/Swal/CustomAlerts.js Целия файл

@@ -0,0 +1,23 @@
import Swal from "sweetalert2";


export const msg = (text) => {
Swal.mixin({
toast: true,
position: "bottom-end",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
}).fire({
icon: "Success",
title: text
});
}

export const popup = (text) => {
Swal.fire(text);
}

+ 62
- 0
src/components/UserWorkspacePage/ProjectGrid.tsx Целия файл

@@ -0,0 +1,62 @@
"use client";
import * as React from "react";
import Grid from "@mui/material/Grid";
import { useEffect, useState } from 'react'
import { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
import {Card,CardContent,CardHeader} from '@mui/material';
import CustomCardGrid from '../CustomCardGrid/CustomCardGrid';
import '../../app/global.css';
import { PROJECT_CARD_STYLE } from "@/theme/colorConst";

interface ProjectGridProps {
tab: number;
}


const ProjectGrid: React.FC<ProjectGridProps> = (props) => {
const [items, setItems] = React.useState<Object[]>([])
useEffect(() => {
if (props.tab == 0) {
setItems(cards)
}
else {
const filteredItems = cards;//cards.filter(item => (item.track == props.tab));
setItems(filteredItems);
}
}, [props.tab]);
const cards = [
{code: 'M1001 (C)', name: 'Consultancy Project A', hr_spent: 12.75, hr_spent_normal: 0.00, hr_alloc: 150.00, hr_alloc_normal: 30.00},
{code: 'M1301 (C)', name: 'Consultancy Project AAA', hr_spent: 4.25, hr_spent_normal: 0.25, hr_alloc: 30.00, hr_alloc_normal: 0.00},
{code: 'M1354 (C)', name: 'Consultancy Project BBB', hr_spent: 57.00, hr_spent_normal: 6.50, hr_alloc: 100.00, hr_alloc_normal: 20.00},
{code: 'M1973 (C)', name: 'Construction Project CCC', hr_spent: 12.75, hr_spent_normal: 0.00, hr_alloc: 150.00, hr_alloc_normal: 30.00},
{code: 'M2014 (T)', name: 'Consultancy Project DDD', hr_spent: 1.00, hr_spent_normal: 0.00, hr_alloc: 10.00, hr_alloc_normal: 0.00},
];
const cardLayout = (item: Record<string, string>) => {
return (
<Card style={PROJECT_CARD_STYLE}>
<CardHeader style={{backgroundColor:'red'}} 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 //
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>
);
};

export default ProjectGrid;

+ 69
- 0
src/components/UserWorkspacePage/UserWorkspacePage.tsx Целия файл

@@ -0,0 +1,69 @@
"use client";
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 { Modal } from "@mui/material";
import CustomModal from "../CustomModal/CustomModal";
import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal";

const UserWorkspacePage: React.FC = () => {
const [isModalVisible, setModalVisible] = useState(false);
const { t } = useTranslation("home");

const handleButtonClick = () => {
setModalVisible(true);
};

const handleCloseModal = () => {
setModalVisible(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={handleButtonClick}
sx={{marginRight:"2rem"}}
>
Enter Timesheet
</Button>
<Button
variant="contained"
startIcon={<Add />}
sx={{marginRight:"2rem"}}
LinkComponent={Link}
href="/projects/create"
>
Record Leave
</Button>
</Stack>
<Suspense> {/*fallback={<ProjectSearch.Loading />}>*/}
</Suspense>
</div>
<EnterTimesheetModal isOpen={isModalVisible} onClose={handleCloseModal}/>
<AssignedProjectGrid Title="Assigned Project"/>
</Grid>
</Grid>
);
};

export default UserWorkspacePage;

+ 1
- 0
src/components/UserWorkspacePage/index.ts Целия файл

@@ -0,0 +1 @@
export { default } from "./UserWorkspacePage";

+ 371
- 0
src/theme/colorConst.js Целия файл

@@ -0,0 +1,371 @@
import { createTheme } from "@mui/material";
import { aborted } from "util";

// - - - - - - WORK IN PROGRESS - - - - - - //

export const chartColor = [
'#CB4047', '#ED3A41', '#F47B50', '#FBA647',
'#FDB64C', '#CCBB32', '#9ACC59', '#57B962',
'#1E83C5', '#7C4A9D'
];

export const chartSingleColor = [
'#f2969a', '#fc9599', '#faa789', '#f7ae94',
'#ffd491', '#ede5a1', '#d1f5a2', '#9de0a4',
'#a2d4f5', '#b685d6'
];

export const rankColor = [
'#FFD700', '#C0C0C0', '#CD853F', '#57B962', '#57B962',
'#57B962', '#57B962', '#57B962', '#57B962', '#57B962'
];

export const piechartColor1 = [
'#E84A3E', '#F2883C', '#FDCD4D', '#CE478A', '#B63D2A',
'#6A8B9E', '#60667E', '#58865F', '#2F763E', '#7D80B5',
];

export const piechartColor2 = [
'#6A8B9E', '#60667E', '#58865F', '#2F763E', '#7D80B5',
'#E84A3E', '#F2883C', '#FDCD4D', '#CE478A', '#B63D2A',
];

export const cardBorderColor = [
'#efb142', '#4bb641', '#448df2', '#e03c04'
];

export const chartLineColor = [
'#FFFFFF', '#D9D9D9'
];

export const GENERAL_RED_COLOR = '#e03c04';

export const TABLE_HEADER_TEXT_COLOR = "#3367D1";

export const GENERAL_INFO_COLOR = '#448df2';

export const GENERAL_SETTING_COLOR = '#666666';

export const GENERAL_BORDER_COLOR = '#e6ebf1';

export const GENERAL_TEXT_COLOR = '#262626';

export const FONT_SIZE_L = "1.875rem";

export const FONT_SIZE_M = "1.5rem";

export const FONT_SIZE_S = "1.25rem";

export const PROJECT_CARD_STYLE = {
borderRadius: '10px',
//border: '10px dotted #ccc',
width: '20rem',
margin: '20px',
//backgroundColor:"pink"
};

export const PROJECT_MODAL_STYLE = {
position: 'absolute',
width: '85%',
borderRadius: '10px',
height: '75%',
// top: '50%',
// left: '50%',
transform: 'translate(10%, 15%)',
backgroundColor: 'white',
padding: '20px',
display: 'flex',
flexDirection: 'column',
};

export const TAB_THEME = {
components: {
MuiTab: {
styleOverrides: {
root: {
// fontSize: '1.0rem',
fontSize: '1.25rem'//'20px',
// height: '40px',
// width: '40vw', // Default width for xs screen sizes
// '@media (min-width: 600px)': { // sm breakpoint
// width: '20vw',
// },
// '@media (min-width: 960px)': { // md breakpoint
// width: '15vw',
// },
// '@media (min-width: 1280px)': { // lg breakpoint
// width: '7vw',
// },
// textTransform: "none",
// alignItems: 'center'
},
},
},
}
};

// copy from MTMS
export const TSMS_BUTTON_THEME = createTheme({
palette: {
primary: {
main: '#92C1E9',
contrastText: '#FFFFFF',
},
secondary: {
main: '#898D8D',
contrastText: '#FFFFFF',
},
success: {
main: '#ADCAB8',
contrastText: '#FFFFFF',
},
danger: {
main: '#F890A5',
contrastText: '#FFFFFF',
},
warning: {
main: '#EFBE7D',
contrastText: '#FFFFFF',
},
disable: {
main: '#B2B4B2',
contrastText: '#FFFFFF',
},
create: {
// main: '#57B962',
main: '#ADCAB8',
// light: will be calculated from palette.primary.main,
// dark: will be calculated from palette.primary.main,
// contrastText: will be calculated to contrast with palette.primary.main
contrastText: '#FFFFFF',
},
delete: {
// main: '#E03C04',
main: '#F890A5',
contrastText: '#FFFFFF',

},
cancel: {
// main: '#999999',
main: '#F890A5',
contrastText: '#FFFFFF',
},
back: {
// main: '#999999',
main: '#898D8D',
contrastText: '#FFFFFF',
},
reset: {
main: '#EFBE7D',
contrastText: '#FFFFFF',
},
save: {
// main: '#448DF2',
main: '#92C1E9',
contrastText: '#FFFFFF',
},
export: {
main: '#8C52FF',
contrastText: '#FFFFFF',
},
import: {
main: '#92C1E9',
contrastText: '#FFFFFF',
},
saveAs: {
main: '#FFBD59',
contrastText: '#FFFFFF',
}
},
components: {
MuiButton: {
styleOverrides: {
root: {
'& .MuiButtonBase-root-MuiButton-root': {
fontSize: FONT_SIZE_S
},
}
}
},
MuiButtonBase: {
styleOverrides: {
root: {
'&.MuiChip-root.Mui-disabled': {
opacity: 0.75,
},
'&.MuiButton-root': {
fontSize: FONT_SIZE_S
},
}
}
},
}
});

export const formTheme = createTheme({
components: {
MuiFormLabel: {
root: { // Name of the rule
color: "rgba(0, 0, 0, 1)",
},
styleOverrides: {
asterisk: {
color: "#db3131",
"&$error": {
color: "#db3131",
},
},
},
},
},
});


export const ARS_BUTTON_THEME = createTheme({
palette: {
create: {
main: '#57B962',
// light: will be calculated from palette.primary.main,
// dark: will be calculated from palette.primary.main,
// contrastText: will be calculated to contrast with palette.primary.main
contrastText: '#FFFFFF',
},
delete: {
main: '#E03C04',
contrastText: '#FFFFFF',

},
cancel: {
main: '#999999',
contrastText: '#FFFFFF',

},
save: {
main: '#448DF2',
contrastText: '#FFFFFF',
},
export: {
main: '#8C52FF',
contrastText: '#FFFFFF',
},
saveAs: {
main: '#FFBD59',
contrastText: '#FFFFFF',
},
edit: {
main: '#F3AF2B',
contrastText: '#FFFFFF',
},
exportExcel: {
main: '#6A8B9E',
contrastText: '#FFFFFF',
}
},
components: {
MuiDataGrid: {
styleOverrides: {
actionsCell: {
'& .MuiDataGrid-actionsContainer .MuiIconButton-root': {
fontSize: '80px', // Set the desired icon size here
},
},
},
},
MuiButton: {
styleOverrides: {
root: {
// fontSize: '1.0rem',
fontSize: '1.25rem',
height: '40px',
width: '40vw', // Default width for xs screen sizes
'@media (min-width: 600px)': { // sm breakpoint
width: '20vw',
},
'@media (min-width: 960px)': { // md breakpoint
width: '15vw',
},
'@media (min-width: 1280px)': { // lg breakpoint
width: '7vw',
},
textTransform: "none",
alignItems: 'center'
},
},
},
}
});

//from ARS
export const TSMS_LONG_BUTTON_THEME = createTheme({
palette: {
create: {
main: '#57B962',
// light: will be calculated from palette.primary.main,
// dark: will be calculated from palette.primary.main,
// contrastText: will be calculated to contrast with palette.primary.main
contrastText: '#FFFFFF',
},
delete: {
main: '#E03C04',
contrastText: '#FFFFFF',

},
cancel: {
main: '#999999',
contrastText: '#FFFFFF',

},
save: {
main: '#448DF2',
contrastText: '#FFFFFF',
},
export: {
main: '#8C52FF',
contrastText: '#FFFFFF',
},
saveAs: {
main: '#FFBD59',
contrastText: '#FFFFFF',
},
edit: {
main: '#F3AF2B',
contrastText: '#FFFFFF',
},
exportExcel: {
main: '#60667E',
contrastText: '#FFFFFF',
}
},
components: {
MuiDataGrid: {
styleOverrides: {
actionsCell: {
'& .MuiDataGrid-actionsContainer .MuiIconButton-root': {
fontSize: '80px', // Set the desired icon size here
},
},
},
},
MuiButton: {
styleOverrides: {
root: {
fontSize: '1.25rem',
height: '40px',
width: '40vw', // Default width for xs screen sizes
'@media (min-width: 600px)': { // sm breakpoint
width: '30vw',
},
'@media (min-width: 960px)': { // md breakpoint
width: '25vw',
},
'@media (min-width: 1280px)': { // lg breakpoint
width: '14vw',
},
textTransform: "none",
alignItems: 'center'
},
},
},
}
});

Зареждане…
Отказ
Запис