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