25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

549 lines
14 KiB

  1. "use client";
  2. import Grid from "@mui/material/Grid";
  3. import Paper from "@mui/material/Paper";
  4. import { useState, useEffect } from "react";
  5. import { useTranslation } from "react-i18next";
  6. import PageTitle from "../PageTitle/PageTitle";
  7. import { Suspense } from "react";
  8. import Button from "@mui/material/Button";
  9. import Stack from "@mui/material/Stack";
  10. import Link from "next/link";
  11. import { t } from "i18next";
  12. import {
  13. Box,
  14. Container,
  15. Modal,
  16. Select,
  17. SelectChangeEvent,
  18. Typography,
  19. } from "@mui/material";
  20. import { Close } from "@mui/icons-material";
  21. import AddIcon from "@mui/icons-material/Add";
  22. import EditIcon from "@mui/icons-material/Edit";
  23. import DeleteIcon from "@mui/icons-material/DeleteOutlined";
  24. import SaveIcon from "@mui/icons-material/Save";
  25. import CancelIcon from "@mui/icons-material/Close";
  26. import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
  27. import ArrowBackIcon from "@mui/icons-material/ArrowBack";
  28. import Swal from "sweetalert2";
  29. import { msg } from "../Swal/CustomAlerts";
  30. import React from "react";
  31. import { DatePicker } from "@mui/x-date-pickers/DatePicker";
  32. import {
  33. GridRowsProp,
  34. GridRowModesModel,
  35. GridRowModes,
  36. DataGrid,
  37. GridColDef,
  38. GridToolbarContainer,
  39. GridFooterContainer,
  40. GridActionsCellItem,
  41. GridEventListener,
  42. GridRowId,
  43. GridRowModel,
  44. GridRowEditStopReasons,
  45. GridEditInputCell,
  46. GridValueSetterParams,
  47. } from "@mui/x-data-grid";
  48. import { LocalizationProvider } from "@mui/x-date-pickers";
  49. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  50. import dayjs from "dayjs";
  51. import { Props } from "react-intl/src/components/relative";
  52. const weekdays = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
  53. interface BottomBarProps {
  54. getHoursTotal: (column: string) => number;
  55. setLockConfirm: (newLock: (oldLock: boolean) => boolean) => void;
  56. setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
  57. setRowModesModel: (
  58. newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
  59. ) => void;
  60. }
  61. interface EditToolbarProps {
  62. // setDay: (newDay : dayjs.Dayjs) => void;
  63. setDay: (newDay: (oldDay: dayjs.Dayjs) => dayjs.Dayjs) => void;
  64. setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
  65. setRowModesModel: (
  66. newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
  67. ) => void;
  68. }
  69. interface EditFooterProps {
  70. setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
  71. setRowModesModel: (
  72. newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
  73. ) => void;
  74. }
  75. const EditToolbar = (props: EditToolbarProps) => {
  76. const { setDay } = props;
  77. const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs>(dayjs());
  78. const handleClickLeft = () => {
  79. if (selectedDate) {
  80. const newDate = selectedDate.add(-7, "day");
  81. setSelectedDate(newDate);
  82. }
  83. };
  84. const handleClickRight = () => {
  85. if (selectedDate) {
  86. const newDate =
  87. selectedDate.add(7, "day") > dayjs()
  88. ? dayjs()
  89. : selectedDate.add(7, "day");
  90. setSelectedDate(newDate);
  91. }
  92. };
  93. const handleDateChange = (date: dayjs.Dayjs | Date | null) => {
  94. const newDate = dayjs(date);
  95. setSelectedDate(newDate);
  96. };
  97. useEffect(() => {
  98. setDay((oldDay) => selectedDate);
  99. }, [selectedDate]);
  100. return (
  101. <LocalizationProvider dateAdapter={AdapterDayjs}>
  102. <div
  103. style={{
  104. display: "flex",
  105. justifyContent: "flex-end",
  106. width: "100%",
  107. paddingBottom: "20px",
  108. }}
  109. >
  110. <Typography variant="h5" id="modal-title" sx={{ flex: 1 }}>
  111. Timesheet Input
  112. </Typography>
  113. <Button
  114. sx={{ "border-radius": "30%", marginRight: "20px" }}
  115. variant="contained"
  116. onClick={handleClickLeft}
  117. >
  118. <ArrowBackIcon />
  119. </Button>
  120. <DatePicker
  121. value={selectedDate}
  122. onChange={handleDateChange}
  123. disableFuture={true}
  124. />
  125. <Button
  126. sx={{ "border-radius": "30%", margin: "0px 20px 0px 20px" }}
  127. variant="contained"
  128. onClick={handleClickRight}
  129. >
  130. <ArrowForwardIcon />
  131. </Button>
  132. </div>
  133. </LocalizationProvider>
  134. );
  135. };
  136. const BottomBar = (props: BottomBarProps) => {
  137. const { setRows, setRowModesModel, getHoursTotal, setLockConfirm } = props;
  138. // const getHoursTotal = props.getHoursTotal;
  139. const [newId, setNewId] = useState(-1);
  140. const [invalidDays, setInvalidDays] = useState(0);
  141. const handleAddClick = () => {
  142. const id = newId;
  143. setNewId(newId - 1);
  144. setRows((oldRows) => [
  145. ...oldRows,
  146. { id, projectCode: "", task: "", isNew: true },
  147. ]);
  148. setRowModesModel((oldModel) => ({
  149. ...oldModel,
  150. [id]: { mode: GridRowModes.Edit, fieldToFocus: "projectCode" },
  151. }));
  152. };
  153. const totalColDef = {
  154. flex: 1,
  155. // style: {color:getHoursTotal('mon')>24?"red":"black"}
  156. };
  157. const TotalCell = ({ value }: Props) => {
  158. const [invalid, setInvalid] = useState(false);
  159. useEffect(() => {
  160. const newInvalid = (value ?? 0) > 24;
  161. setInvalid(newInvalid);
  162. }, [value]);
  163. return (
  164. <Box flex={1} style={{ color: invalid ? "red" : "black" }}>
  165. {value}
  166. </Box>
  167. );
  168. };
  169. const checkUnlockConfirmBtn = () => {
  170. // setLockConfirm((oldLock)=> valid);
  171. setLockConfirm((oldLock) =>
  172. weekdays.every((weekday) => {
  173. getHoursTotal(weekday) <= 24;
  174. }),
  175. );
  176. };
  177. return (
  178. <div>
  179. <div style={{ display: "flex", justifyContent: "flex", width: "100%" }}>
  180. <Box flex={5.7} textAlign={"right"} marginRight="4rem">
  181. <b>Total:</b>
  182. </Box>
  183. <TotalCell value={getHoursTotal("mon")} />
  184. <TotalCell value={getHoursTotal("tue")} />
  185. <TotalCell value={getHoursTotal("wed")} />
  186. <TotalCell value={getHoursTotal("thu")} />
  187. <TotalCell value={getHoursTotal("fri")} />
  188. <TotalCell value={getHoursTotal("sat")} />
  189. <TotalCell value={getHoursTotal("sun")} />
  190. </div>
  191. <Button
  192. variant="outlined"
  193. color="primary"
  194. startIcon={<AddIcon />}
  195. onClick={handleAddClick}
  196. >
  197. Add record
  198. </Button>
  199. </div>
  200. );
  201. };
  202. const EditFooter = (props: EditFooterProps) => {
  203. return (
  204. <div style={{ display: "flex", justifyContent: "flex", width: "100%" }}>
  205. <Box flex={1}>
  206. <b>Total: </b>
  207. </Box>
  208. <Box flex={2}>ssss</Box>
  209. </div>
  210. );
  211. };
  212. interface TimesheetInputGridProps {
  213. setLockConfirm: (newLock: (oldLock: boolean) => boolean) => void;
  214. onClose?: () => void;
  215. }
  216. const initialRows: GridRowsProp = [
  217. {
  218. id: 1,
  219. projectCode: "M1001",
  220. task: "1.2",
  221. mon: 2.5,
  222. },
  223. {
  224. id: 2,
  225. projectCode: "M1002",
  226. task: "1.3",
  227. mon: 3.25,
  228. },
  229. ];
  230. const options = ["M1001", "M1301", "M1354", "M1973"];
  231. const options2 = [
  232. "1.1 - Preparation of preliminary Cost Estimate / Cost Plan",
  233. "1.2 - Cash flow forecast",
  234. "1.3 - Cost studies fo alterative design solutions",
  235. "1.4 = Attend design co-ordination / project review meetings",
  236. "1.5 - Prepare / Review RIC",
  237. ];
  238. const getDateForHeader = (date: dayjs.Dayjs, weekday: number) => {
  239. if (date.day() == 0) {
  240. return date.add(weekday - date.day() - 7, "day").format("DD MMM");
  241. } else {
  242. return date.add(weekday - date.day(), "day").format("DD MMM");
  243. }
  244. };
  245. const TimesheetInputGrid: React.FC<TimesheetInputGridProps> = ({
  246. ...props
  247. }) => {
  248. const [rows, setRows] = useState(initialRows);
  249. const [day, setDay] = useState(dayjs());
  250. const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>(
  251. {},
  252. );
  253. const { setLockConfirm } = props;
  254. const handleRowEditStop: GridEventListener<"rowEditStop"> = (
  255. params,
  256. event,
  257. ) => {
  258. if (params.reason === GridRowEditStopReasons.rowFocusOut) {
  259. event.defaultMuiPrevented = true;
  260. }
  261. };
  262. const handleEditClick = (id: GridRowId) => () => {
  263. setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
  264. };
  265. const handleSaveClick = (id: GridRowId) => () => {
  266. setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
  267. };
  268. const handleDeleteClick = (id: GridRowId) => () => {
  269. setRows(rows.filter((row) => row.id !== id));
  270. };
  271. const handleCancelClick = (id: GridRowId) => () => {
  272. setRowModesModel({
  273. ...rowModesModel,
  274. [id]: { mode: GridRowModes.View, ignoreModifications: true },
  275. });
  276. const editedRow = rows.find((row) => row.id === id);
  277. if (editedRow!.isNew) {
  278. setRows(rows.filter((row) => row.id !== id));
  279. }
  280. };
  281. const processRowUpdate = (newRow: GridRowModel) => {
  282. const updatedRow = { ...newRow, isNew: false };
  283. setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
  284. return updatedRow;
  285. };
  286. const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
  287. setRowModesModel(newRowModesModel);
  288. };
  289. const getHoursTotal = (column: any) => {
  290. let sum = 0;
  291. rows.forEach((row) => {
  292. sum += row[column] ?? 0;
  293. });
  294. return sum;
  295. };
  296. const weekdayColConfig: any = {
  297. type: "number",
  298. // sortable: false,
  299. //width: 100,
  300. flex: 1,
  301. align: "left",
  302. headerAlign: "left",
  303. editable: true,
  304. renderEditCell: (value: any) => (
  305. <GridEditInputCell
  306. {...value}
  307. inputProps={{
  308. max: 24,
  309. min: 0,
  310. step: 0.25,
  311. }}
  312. />
  313. ),
  314. };
  315. const columns: GridColDef[] = [
  316. {
  317. field: "actions",
  318. type: "actions",
  319. headerName: "Actions",
  320. width: 100,
  321. cellClassName: "actions",
  322. getActions: ({ id }) => {
  323. const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
  324. if (isInEditMode) {
  325. return [
  326. <GridActionsCellItem
  327. key={`actions-${id}-save`}
  328. icon={<SaveIcon />}
  329. title="Save"
  330. label="Save"
  331. sx={{
  332. color: "primary.main",
  333. }}
  334. onClick={handleSaveClick(id)}
  335. />,
  336. <GridActionsCellItem
  337. key={`actions-${id}-cancel`}
  338. icon={<CancelIcon />}
  339. title="Cancel"
  340. label="Cancel"
  341. className="textPrimary"
  342. onClick={handleCancelClick(id)}
  343. color="inherit"
  344. />,
  345. ];
  346. }
  347. return [
  348. <GridActionsCellItem
  349. key={`actions-${id}-edit`}
  350. icon={<EditIcon />}
  351. title="Edit"
  352. label="Edit"
  353. className="textPrimary"
  354. onClick={handleEditClick(id)}
  355. color="inherit"
  356. />,
  357. <GridActionsCellItem
  358. key={`actions-${id}-delete`}
  359. title="Delete"
  360. label="Delete"
  361. icon={<DeleteIcon />}
  362. onClick={handleDeleteClick(id)}
  363. sx={{ color: "red" }}
  364. />,
  365. ];
  366. },
  367. },
  368. {
  369. field: "projectCode",
  370. headerName: "Project Code",
  371. // width: 220,
  372. flex: 2,
  373. editable: true,
  374. type: "singleSelect",
  375. valueOptions: options,
  376. },
  377. {
  378. field: "task",
  379. headerName: "Task",
  380. // width: 220,
  381. flex: 3,
  382. editable: true,
  383. type: "singleSelect",
  384. valueOptions: options2,
  385. },
  386. {
  387. // Mon
  388. field: "mon",
  389. ...weekdayColConfig,
  390. renderHeader: () => {
  391. return <div>Mon - {getDateForHeader(day, 1)}</div>;
  392. },
  393. },
  394. {
  395. // Tue
  396. field: "tue",
  397. ...weekdayColConfig,
  398. renderHeader: () => {
  399. return <div>Tue - {getDateForHeader(day, 2)}</div>;
  400. },
  401. },
  402. {
  403. // Wed
  404. field: "wed",
  405. ...weekdayColConfig,
  406. renderHeader: () => {
  407. return <div>Wed - {getDateForHeader(day, 3)}</div>;
  408. },
  409. },
  410. {
  411. // Thu
  412. field: "thu",
  413. ...weekdayColConfig,
  414. renderHeader: () => {
  415. return <div>Thu - {getDateForHeader(day, 4)}</div>;
  416. },
  417. },
  418. {
  419. // Fri
  420. field: "fri",
  421. ...weekdayColConfig,
  422. renderHeader: () => {
  423. return <div>Fri - {getDateForHeader(day, 5)}</div>;
  424. },
  425. },
  426. {
  427. // Sat
  428. field: "sat",
  429. ...weekdayColConfig,
  430. renderHeader: () => {
  431. return <div>Sat - {getDateForHeader(day, 6)}</div>;
  432. },
  433. },
  434. {
  435. // Sun
  436. field: "sun",
  437. ...weekdayColConfig,
  438. renderHeader: () => {
  439. return (
  440. <div style={{ color: "red" }}>Sun - {getDateForHeader(day, 7)}</div>
  441. );
  442. },
  443. },
  444. // {
  445. // field: 'joinDate',
  446. // headerName: 'Join date',
  447. // type: 'date',
  448. // width: 180,
  449. // editable: true,
  450. // },
  451. ];
  452. return (
  453. <Box
  454. sx={{
  455. // marginBottom: '-5px',
  456. display: "flex",
  457. "flex-direction": "column",
  458. // 'justify-content': 'flex-end',
  459. padding: "20px",
  460. height: "100%", //'30rem',
  461. width: "100%",
  462. "& .actions": {
  463. color: "text.secondary",
  464. },
  465. "& .header": {
  466. // border: 1,
  467. // 'border-width': '1px',
  468. // 'border-color': 'grey',
  469. },
  470. "& .textPrimary": {
  471. color: "text.primary",
  472. },
  473. }}
  474. >
  475. <DataGrid
  476. rows={rows}
  477. columns={columns}
  478. editMode="row"
  479. rowModesModel={rowModesModel}
  480. onRowModesModelChange={handleRowModesModelChange}
  481. onRowEditStop={handleRowEditStop}
  482. processRowUpdate={processRowUpdate}
  483. disableRowSelectionOnClick={true}
  484. disableColumnMenu={true}
  485. hideFooterPagination={true}
  486. slots={{
  487. toolbar: EditToolbar,
  488. // footer: EditFooter,
  489. }}
  490. slotProps={{
  491. toolbar: { setDay, setRows, setRowModesModel },
  492. // footer: { setDay, setRows, setRowModesModel },
  493. }}
  494. initialState={{
  495. pagination: { paginationModel: { pageSize: 100 } },
  496. }}
  497. sx={{ flex: 1 }}
  498. />
  499. <BottomBar
  500. getHoursTotal={getHoursTotal}
  501. setRows={setRows}
  502. setRowModesModel={setRowModesModel}
  503. setLockConfirm={setLockConfirm}
  504. // sx={{flex:3}}
  505. />
  506. </Box>
  507. );
  508. };
  509. export default TimesheetInputGrid;