|
- import { TimeEntry } from "@/app/api/timesheets/actions";
- import { Check, Delete } from "@mui/icons-material";
- import {
- Box,
- Button,
- FormControl,
- InputLabel,
- Modal,
- ModalProps,
- Paper,
- SxProps,
- TextField,
- } from "@mui/material";
- import React, { useCallback, useEffect, useMemo } from "react";
- import { Controller, useForm } from "react-hook-form";
- import { useTranslation } from "react-i18next";
- import ProjectSelect from "./ProjectSelect";
- import { AssignedProject, ProjectWithTasks } from "@/app/api/projects";
- import TaskGroupSelect from "./TaskGroupSelect";
- import TaskSelect from "./TaskSelect";
- import { TaskGroup } from "@/app/api/tasks";
- import uniqBy from "lodash/uniqBy";
-
- export interface Props extends Omit<ModalProps, "children"> {
- onSave: (leaveEntry: TimeEntry) => void;
- onDelete?: () => void;
- defaultValues?: Partial<TimeEntry>;
- allProjects: ProjectWithTasks[];
- assignedProjects: AssignedProject[];
- }
-
- const modalSx: SxProps = {
- position: "absolute",
- top: "50%",
- left: "50%",
- transform: "translate(-50%, -50%)",
- width: "90%",
- maxHeight: "90%",
- padding: 3,
- display: "flex",
- flexDirection: "column",
- gap: 2,
- };
- const TimesheetEditModal: React.FC<Props> = ({
- onSave,
- onDelete,
- open,
- onClose,
- defaultValues,
- allProjects,
- assignedProjects,
- }) => {
- const { t } = useTranslation("home");
-
- const taskGroupsByProject = useMemo(() => {
- return allProjects.reduce<{
- [projectId: AssignedProject["id"]]: {
- value: TaskGroup["id"];
- label: string;
- }[];
- }>((acc, project) => {
- return {
- ...acc,
- [project.id]: uniqBy(
- project.tasks.map((t) => ({
- value: t.taskGroup.id,
- label: t.taskGroup.name,
- })),
- "value",
- ),
- };
- }, {});
- }, [allProjects]);
-
- const {
- register,
- control,
- reset,
- getValues,
- setValue,
- trigger,
- formState,
- watch,
- } = useForm<TimeEntry>();
-
- useEffect(() => {
- reset(defaultValues ?? { id: Date.now() });
- }, [defaultValues, reset]);
-
- const saveHandler = useCallback(async () => {
- const valid = await trigger();
- if (valid) {
- onSave(getValues());
- reset();
- }
- }, [getValues, onSave, reset, trigger]);
-
- const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
- (...args) => {
- onClose?.(...args);
- reset();
- },
- [onClose, reset],
- );
-
- const projectId = watch("projectId");
- const taskGroupId = watch("taskGroupId");
- const otHours = watch("otHours");
-
- return (
- <Modal open={open} onClose={closeHandler}>
- <Paper sx={modalSx}>
- <FormControl fullWidth>
- <InputLabel shrink>{t("Project Code and Name")}</InputLabel>
- <Controller
- control={control}
- name="projectId"
- render={({ field }) => (
- <ProjectSelect
- allProjects={allProjects}
- assignedProjects={assignedProjects}
- value={field.value}
- onProjectSelect={(newId) => {
- field.onChange(newId ?? null);
- const firstTaskGroup = (
- typeof newId === "number" ? taskGroupsByProject[newId] : []
- )[0];
-
- setValue("taskGroupId", firstTaskGroup?.value);
- setValue("taskId", undefined);
- }}
- />
- )}
- rules={{ deps: ["taskGroupId", "taskId"] }}
- />
- </FormControl>
- <FormControl fullWidth>
- <InputLabel shrink>{t("Stage")}</InputLabel>
- <Controller
- control={control}
- name="taskGroupId"
- render={({ field }) => (
- <TaskGroupSelect
- error={Boolean(formState.errors.taskGroupId)}
- projectId={projectId}
- taskGroupsByProject={taskGroupsByProject}
- value={field.value}
- onTaskGroupSelect={(newId) => {
- field.onChange(newId ?? null);
- }}
- />
- )}
- rules={{
- validate: (id) => {
- if (!projectId) {
- return !id;
- }
- const taskGroups = taskGroupsByProject[projectId];
- return taskGroups.some((tg) => tg.value === id);
- },
- deps: ["taskId"],
- }}
- />
- </FormControl>
- <FormControl fullWidth>
- <InputLabel shrink>{t("Task")}</InputLabel>
- <Controller
- control={control}
- name="taskId"
- render={({ field }) => (
- <TaskSelect
- error={Boolean(formState.errors.taskId)}
- projectId={projectId}
- taskGroupId={taskGroupId}
- allProjects={allProjects}
- value={field.value}
- onTaskSelect={(newId) => {
- field.onChange(newId ?? null);
- }}
- />
- )}
- rules={{
- validate: (id) => {
- if (!projectId) {
- return !id;
- }
- const projectTasks = allProjects.find((p) => p.id === projectId)
- ?.tasks;
- return Boolean(projectTasks?.some((task) => task.id === id));
- },
- }}
- />
- </FormControl>
- <TextField
- type="number"
- label={t("Hours")}
- fullWidth
- {...register("inputHours", {
- valueAsNumber: true,
- validate: (value) => Boolean(value || otHours),
- })}
- error={Boolean(formState.errors.inputHours)}
- />
- <TextField
- type="number"
- label={t("Other Hours")}
- fullWidth
- {...register("otHours", {
- valueAsNumber: true,
- })}
- error={Boolean(formState.errors.otHours)}
- />
- <TextField
- label={t("Remark")}
- fullWidth
- multiline
- rows={2}
- error={Boolean(formState.errors.remark)}
- {...register("remark", {
- validate: (value) => Boolean(projectId || value),
- })}
- />
- <Box display="flex" justifyContent="flex-end" gap={1}>
- {onDelete && (
- <Button
- variant="outlined"
- startIcon={<Delete />}
- color="error"
- onClick={onDelete}
- >
- {t("Delete")}
- </Button>
- )}
- <Button
- variant="contained"
- startIcon={<Check />}
- onClick={saveHandler}
- >
- {t("Save")}
- </Button>
- </Box>
- </Paper>
- </Modal>
- );
- };
-
- export default TimesheetEditModal;
|