Bläddra i källkod

fix timesheet input modal layout

tags/Baseline_30082024_FRONTEND_UAT
kelvinsuen 1 år sedan
förälder
incheckning
0fab96378e
5 ändrade filer med 750 tillägg och 36 borttagningar
  1. +125
    -0
      src/components/EnterLeave/EnterLeaveModal.tsx
  2. +548
    -0
      src/components/EnterLeave/LeaveInputGrid.tsx
  3. +1
    -0
      src/components/EnterLeave/index.ts
  4. +51
    -26
      src/components/EnterTimesheet/EnterTimesheetModal.tsx
  5. +25
    -10
      src/components/UserWorkspacePage/UserWorkspacePage.tsx

+ 125
- 0
src/components/EnterLeave/EnterLeaveModal.tsx Visa fil

@@ -0,0 +1,125 @@
"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 TimesheetInputGrid from "./LeaveInputGrid";
import { BASE_API_URL } from "@/config/api";

// import { fetchLeaves } from "@/app/api/leave";

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",
},
];

const fetchTimesheet = async () => {
// fetchLeaves();
// const res = await fetch(`http://localhost:8090/api/timesheets`, {
// // const res = await fetch(`${BASE_API_URL}/timesheets`, {
// method: "GET",
// mode: 'no-cors',
// });

// console.log(res.json);
};

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

<Card style={{
flex: 10,
marginBottom: "20px",
width: "90%",
// height: "80%",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}>
<TimesheetInputGrid setLockConfirm={setLockConfirm}/>
<div
style={{
display: "flex",
justifyContent: "space-between",
width: "100%",
flex: 1,
padding: "20px",
}}
>
<Button
disabled={lockConfirm}
variant="contained"
onClick={props.onClose}
>
Confirm
</Button>
<Button
variant="contained"
onClick={props.onClose}
sx={{ "background-color": "#F890A5" }}
>
Cancel
</Button>
</div>
</Card>
</div>
</Modal>
);
};

export default EnterTimesheetModal;

+ 548
- 0
src/components/EnterLeave/LeaveInputGrid.tsx Visa fil

@@ -0,0 +1,548 @@
"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 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 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 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%",
paddingBottom: "20px",
}}
>
<Typography variant="h5" id="modal-title" sx={{ flex: 1 }}>
Record Leave
</Typography>
<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 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 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: "Actions",
width: 100,
cellClassName: "actions",
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

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

return [
<GridActionsCellItem
key={`actions-${id}-edit`}
icon={<EditIcon />}
title="Edit"
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem
key={`actions-${id}-delete`}
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',
display: "flex",
"flex-direction": "column",
// 'justify-content': 'flex-end',
padding: "20px",
height: "100%", //'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 } },
}}
sx={{ flex: 1 }}
/>

<BottomBar
getHoursTotal={getHoursTotal}
setRows={setRows}
setRowModesModel={setRowModesModel}
setLockConfirm={setLockConfirm}
// sx={{flex:3}}
/>
</Box>
);
};

export default TimesheetInputGrid;

+ 1
- 0
src/components/EnterLeave/index.ts Visa fil

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

+ 51
- 26
src/components/EnterTimesheet/EnterTimesheetModal.tsx Visa fil

@@ -1,4 +1,6 @@
"use client";

// import { testing } from "@/app/api/timesheets";
import Grid from "@mui/material/Grid";
import Paper from "@mui/material/Paper";
import { useState } from "react";
@@ -17,6 +19,9 @@ import { PROJECT_MODAL_STYLE } from "@/theme/colorConst";
import CustomDatagrid from "../CustomDatagrid/CustomDatagrid";
import { DataGrid } from "@mui/x-data-grid";
import TimesheetInputGrid from "./TimesheetInputGrid";
import { BASE_API_URL } from "@/config/api";

import { fetchTimesheets } from "@/app/api/timesheets";

interface EnterTimesheetModalProps {
isOpen: boolean;
@@ -56,6 +61,17 @@ const EnterTimesheetModal: React.FC<EnterTimesheetModalProps> = ({
},
];

const fetchTimesheet = async () => {
fetchTimesheets();
// const res = await fetch(`http://localhost:8090/api/timesheets`, {
// // const res = await fetch(`${BASE_API_URL}/timesheets`, {
// method: "GET",
// mode: 'no-cors',
// });

// console.log(res.json);
};

return (
<Modal open={props.isOpen} onClose={props.onClose}>
<div>
@@ -65,33 +81,42 @@ const EnterTimesheetModal: React.FC<EnterTimesheetModalProps> = ({
</div>
</Typography> */}

<Card style={{ flex: 10, marginBottom: "20px" }}>
{/* <TimesheetInputGrid setLockConfirm={setLockConfirm}/> */}
</Card>

<div
style={{
display: "flex",
justifyContent: "space-between",
width: "100%",
flex: 1,
}}
>
<Button
disabled={lockConfirm}
variant="contained"
onClick={props.onClose}
<Card style={{
flex: 10,
marginBottom: "20px",
width: "90%",
// height: "80%",
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}}>
<TimesheetInputGrid setLockConfirm={setLockConfirm}/>
<div
style={{
display: "flex",
justifyContent: "space-between",
width: "100%",
flex: 1,
padding: "20px",
}}
>
Confirm
</Button>
<Button
variant="contained"
onClick={props.onClose}
sx={{ "background-color": "#F890A5" }}
>
Cancel
</Button>
</div>
<Button
disabled={lockConfirm}
variant="contained"
onClick={props.onClose}
>
Confirm
</Button>
<Button
variant="contained"
onClick={props.onClose}
sx={{ "background-color": "#F890A5" }}
>
Cancel
</Button>
</div>
</Card>
</div>
</Modal>
);


+ 25
- 10
src/components/UserWorkspacePage/UserWorkspacePage.tsx Visa fil

@@ -14,17 +14,27 @@ import { t } from "i18next";
import { Modal } from "@mui/material";
import CustomModal from "../CustomModal/CustomModal";
import EnterTimesheetModal from "../EnterTimesheet/EnterTimesheetModal";
import EnterLeaveModal from "../EnterLeave/EnterLeaveModal";

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

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

const handleCloseModal = () => {
setModalVisible(false);
const handleCloseTimesheetModal = () => {
setTimeheetModalVisible(false);
};

const handleAddLeaveButtonClick = () => {
setLeaveModalVisible(true);
};

const handleCloseLeaveModal = () => {
setLeaveModalVisible(false);
};

return (
@@ -36,7 +46,7 @@ const UserWorkspacePage: React.FC = () => {
<Button
variant="contained"
startIcon={<Add />}
onClick={handleButtonClick}
onClick={handleAddTimesheetButtonClick}
sx={{ marginRight: "2rem" }}
>
Enter Timesheet
@@ -45,8 +55,9 @@ const UserWorkspacePage: React.FC = () => {
variant="contained"
startIcon={<Add />}
sx={{ marginRight: "2rem" }}
LinkComponent={Link}
href="/projects/create"
// LinkComponent={Link}
// href="/projects/create"
onClick={handleAddLeaveButtonClick}
>
Record Leave
</Button>
@@ -54,8 +65,12 @@ const UserWorkspacePage: React.FC = () => {
<Suspense> {/*fallback={<ProjectSearch.Loading />}>*/}</Suspense>
</div>
<EnterTimesheetModal
isOpen={isModalVisible}
onClose={handleCloseModal}
isOpen={isTimeheetModalVisible}
onClose={handleCloseTimesheetModal}
/>
<EnterLeaveModal
isOpen={isLeaveModalVisible}
onClose={handleCloseLeaveModal}
/>
<AssignedProjectGrid Title="Assigned Project" />
</Grid>


Laddar…
Avbryt
Spara