From 0fab96378e9ad35ad54df3550682665882c08bf5 Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Mon, 11 Mar 2024 05:15:58 +0800 Subject: [PATCH] fix timesheet input modal layout --- src/components/EnterLeave/EnterLeaveModal.tsx | 125 ++++ src/components/EnterLeave/LeaveInputGrid.tsx | 548 ++++++++++++++++++ src/components/EnterLeave/index.ts | 1 + .../EnterTimesheet/EnterTimesheetModal.tsx | 77 ++- .../UserWorkspacePage/UserWorkspacePage.tsx | 35 +- 5 files changed, 750 insertions(+), 36 deletions(-) create mode 100644 src/components/EnterLeave/EnterLeaveModal.tsx create mode 100644 src/components/EnterLeave/LeaveInputGrid.tsx create mode 100644 src/components/EnterLeave/index.ts diff --git a/src/components/EnterLeave/EnterLeaveModal.tsx b/src/components/EnterLeave/EnterLeaveModal.tsx new file mode 100644 index 0000000..a42322c --- /dev/null +++ b/src/components/EnterLeave/EnterLeaveModal.tsx @@ -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 = ({ + ...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 ( + +
+ {/* +
+ Record Leave +
+
*/} + + + +
+ + +
+
+
+
+ ); +}; + +export default EnterTimesheetModal; diff --git a/src/components/EnterLeave/LeaveInputGrid.tsx b/src/components/EnterLeave/LeaveInputGrid.tsx new file mode 100644 index 0000000..03eeaab --- /dev/null +++ b/src/components/EnterLeave/LeaveInputGrid.tsx @@ -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()); + + 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 ( + +
+ + Record Leave + + + + +
+
+ ); +}; + +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 ( + + {value} + + ); + }; + + const checkUnlockConfirmBtn = () => { + // setLockConfirm((oldLock)=> valid); + setLockConfirm((oldLock) => + weekdays.every((weekday) => { + getHoursTotal(weekday) <= 24; + }), + ); + }; + + return ( +
+
+ + Total: + + + + + + + + +
+ +
+ ); +}; + +const EditFooter = (props: EditFooterProps) => { + return ( +
+ + Total: + + ssss +
+ ); +}; + +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 = ({ + ...props +}) => { + const [rows, setRows] = useState(initialRows); + const [day, setDay] = useState(dayjs()); + const [rowModesModel, setRowModesModel] = React.useState( + {}, + ); + 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) => ( + + ), + }; + + 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 [ + } + title="Save" + label="Save" + sx={{ + color: "primary.main", + }} + onClick={handleSaveClick(id)} + />, + } + title="Cancel" + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + />, + ]; + } + + return [ + } + title="Edit" + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + />, + } + 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
Mon - {getDateForHeader(day, 1)}
; + }, + }, + { + // Tue + field: "tue", + ...weekdayColConfig, + renderHeader: () => { + return
Tue - {getDateForHeader(day, 2)}
; + }, + }, + { + // Wed + field: "wed", + ...weekdayColConfig, + renderHeader: () => { + return
Wed - {getDateForHeader(day, 3)}
; + }, + }, + { + // Thu + field: "thu", + ...weekdayColConfig, + renderHeader: () => { + return
Thu - {getDateForHeader(day, 4)}
; + }, + }, + { + // Fri + field: "fri", + ...weekdayColConfig, + renderHeader: () => { + return
Fri - {getDateForHeader(day, 5)}
; + }, + }, + { + // Sat + field: "sat", + ...weekdayColConfig, + renderHeader: () => { + return
Sat - {getDateForHeader(day, 6)}
; + }, + }, + { + // Sun + field: "sun", + ...weekdayColConfig, + renderHeader: () => { + return ( +
Sun - {getDateForHeader(day, 7)}
+ ); + }, + }, + // { + // field: 'joinDate', + // headerName: 'Join date', + // type: 'date', + // width: 180, + // editable: true, + // }, + ]; + + return ( + + + + + + ); +}; + +export default TimesheetInputGrid; diff --git a/src/components/EnterLeave/index.ts b/src/components/EnterLeave/index.ts new file mode 100644 index 0000000..33541f2 --- /dev/null +++ b/src/components/EnterLeave/index.ts @@ -0,0 +1 @@ +export { default } from "./EnterLeaveModal"; diff --git a/src/components/EnterTimesheet/EnterTimesheetModal.tsx b/src/components/EnterTimesheet/EnterTimesheetModal.tsx index 841881a..b76ae98 100644 --- a/src/components/EnterTimesheet/EnterTimesheetModal.tsx +++ b/src/components/EnterTimesheet/EnterTimesheetModal.tsx @@ -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 = ({ }, ]; + 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 (
@@ -65,33 +81,42 @@ const EnterTimesheetModal: React.FC = ({
*/} - - {/* */} - - -
- - -
+ + + +
); diff --git a/src/components/UserWorkspacePage/UserWorkspacePage.tsx b/src/components/UserWorkspacePage/UserWorkspacePage.tsx index e0f6072..8fdb1cf 100644 --- a/src/components/UserWorkspacePage/UserWorkspacePage.tsx +++ b/src/components/UserWorkspacePage/UserWorkspacePage.tsx @@ -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 = () => { @@ -54,8 +65,12 @@ const UserWorkspacePage: React.FC = () => { {/*fallback={}>*/} +