diff --git a/web/.eslintrc.js b/web/.eslintrc.js index 9372a20..cfc8727 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -2,5 +2,6 @@ module.exports = { extends: ["eslint:recommended"], rules: { "no-undef": "off", + "no-unused-vars": "off" }, }; diff --git a/web/package.json b/web/package.json index 9be89ca..227d7b2 100644 --- a/web/package.json +++ b/web/package.json @@ -14,14 +14,17 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.6", "@mui/material": "^5.14.3", + "@mui/x-date-pickers": "^6.12.1", "@types/node": "20.4.8", "@types/react": "18.2.18", "@types/react-dom": "18.2.7", "eslint-config-next": "13.4.12", "jest-junit": "^16.0.0", + "moment": "^2.29.4", "next": "13.4.12", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "^7.46.1", "typescript": "5.1.6" }, "devDependencies": { diff --git a/web/src/app/job/detail/context.tsx b/web/src/app/job/detail/context.tsx new file mode 100644 index 0000000..ff52cff --- /dev/null +++ b/web/src/app/job/detail/context.tsx @@ -0,0 +1,86 @@ +import { FormValues } from "@/utils/constants/FormInitialDummyValues"; +import { + CustomerFormType, + FormValuesType, + InformationFormType, + ScheduleFormType, +} from "@/utils/interfaces"; +import React, { useState, ReactNode, createContext, useContext } from "react"; + +// Create the context +const JobDetailContext = createContext({ + // For Job Customer Section + customerDetails: FormValues.customer_registration, + setCustomerDetails: (newButtonState: CustomerFormType) => { + useState(newButtonState); + }, + + // For Job Information Section + informationDetails: FormValues.job_information, + setInformationDetails: (newJobInformation: InformationFormType) => { + useState(newJobInformation); + }, + + // For Job Schedule + scheduleDetails: FormValues.work_schedule, + setScheduleDetails: (newSchedule: ScheduleFormType[]) => { + useState(newSchedule); + }, + + // For Final object + formValues: FormValues, + setFormValues: (newFormValue: FormValuesType) => { + useState(newFormValue); + }, + + // For Update button state + buttonState: false, + setButtonState: (newButtonState: boolean) => { + useState(newButtonState); + }, +}); + +// Create a provider component +const JobDetailContextProvider = ({ children }: { children: ReactNode }) => { + const { customer_registration, job_information, work_schedule } = FormValues; + + const [customerDetails, setCustomerDetails] = useState( + customer_registration + ); + const [informationDetails, setInformationDetails] = + useState(job_information); + const [scheduleDetails, setScheduleDetails] = + useState(work_schedule); + const [buttonState, setButtonState] = useState(false); + const [formValues, setFormValues] = useState(FormValues); + + return ( + + {children} + + ); +}; + +export { JobDetailContext, JobDetailContextProvider }; + +export function useJobDetailContext() { + const context = useContext(JobDetailContext); + if (!context) { + throw new Error("Custom hook in JobDetailContextProvider"); + } + + return context; +} diff --git a/web/src/app/job/detail/hooks.tsx b/web/src/app/job/detail/hooks.tsx new file mode 100644 index 0000000..5a07d29 --- /dev/null +++ b/web/src/app/job/detail/hooks.tsx @@ -0,0 +1,75 @@ +import { + CustomerFormType, + FormValuesType, + InformationFormType, + ScheduleFormType, +} from "@/utils/interfaces"; +import { usePathname } from "next/navigation"; +import { Link, Typography } from "@mui/material"; +import { Dispatch, SetStateAction } from "react"; + +export const breadCrumbs = (): JSX.Element[] => { + const pathname = usePathname(); + + const pathArray = pathname.split("/").map((crumb, index, array) => { + if (index === 0) return "Home"; + if (index === array.length - 1) return "Job Detail"; + return crumb; + }); + + return pathArray.map((breadcrumb, index) => + index === pathArray.length - 1 ? ( + + {breadcrumb} + + ) : ( + + {breadcrumb} + + ) + ); +}; + +export const handleCustomerData = ( + data: CustomerFormType, + setButtonState: Dispatch>, + formValues: FormValuesType, + setFormValues: Dispatch> +): void => { + setButtonState(true); + setFormValues({ + ...formValues, + ["customer_registration"]: data, + }); +}; + +export const handleJobDetailData = ( + data: InformationFormType, + setButtonState: Dispatch>, + formValues: FormValuesType, + setFormValues: Dispatch> +) => { + setButtonState(true); + setFormValues({ + ...formValues, + ["job_information"]: data, + }); +}; + +export const handleWorkScheduleData = ( + data: ScheduleFormType, + setButtonState: Dispatch>, + formValues: FormValuesType, + setFormValues: Dispatch> +) => { + setButtonState(true); + setFormValues({ + ...formValues, + ["work_schedule"]: [data], + }); +}; diff --git a/web/src/app/job/detail/page.tsx b/web/src/app/job/detail/page.tsx new file mode 100644 index 0000000..998e866 --- /dev/null +++ b/web/src/app/job/detail/page.tsx @@ -0,0 +1,40 @@ +"use client"; + +import React from "react"; +import { Box, Typography } from "@mui/material"; +import { BreadcrumbsSection } from "@/components/organisms/BreadCrumbsSection"; + +import { breadCrumbs } from "./hooks"; +import { JobDetailContextProvider } from "./context"; +import { SubmitButton } from "@/components/organisms/SubmitButton"; +import JobDetailSection from "@/components/organisms/UpdateJobDetailSection"; +import JobWorkScheduleSection from "@/components/organisms/UpdateJobScheduleSection"; +import JobCustomerSection from "../../../components/organisms/UpdateJobCustomerSection"; + +export default function Job() { + return ( + +
+ + + + Job Detail + + + + + + +
+
+ ); +} diff --git a/web/src/app/job/page.tsx b/web/src/app/job/page.tsx new file mode 100644 index 0000000..e04eb12 --- /dev/null +++ b/web/src/app/job/page.tsx @@ -0,0 +1,41 @@ +"use client"; + +import React from "react"; +import { Box, Button, Typography } from "@mui/material"; + +import JobCustomerSection from "../../components/organisms/JobCustomerSection"; +import JobInformationSection from "../../components/organisms/JobInformationSection"; +import JobWorkScheduleSection from "../../components/organisms/JobWorkScheduleSection"; + +export default function Job() { + const handleSubmit = () => { + console.log("submitted form"); + }; + + return ( +
+ + Job Creation + + + + + + + + + +
+ ); +} diff --git a/web/src/assets/theme.ts b/web/src/assets/theme.ts index 703245e..022d1bc 100644 --- a/web/src/assets/theme.ts +++ b/web/src/assets/theme.ts @@ -93,6 +93,9 @@ const customDefaultFontFamily = { export const theme = createTheme({ palette: { ...customColors }, typography: { + button: { + textTransform: 'none', + }, allVariants: { ...customDefaultFontFamily, color: customColors.dark, @@ -213,3 +216,13 @@ export const theme = createTheme({ }, }, }); + +declare module '@mui/material/TextField' { + interface TextFieldPropsColorOverrides { + light: true; + dark: true; + neutral: true; + disabled: true; + white: true; + } +} diff --git a/web/src/components/molecules/ModeOfPaymentsRadioGroup.tsx b/web/src/components/molecules/ModeOfPaymentsRadioGroup.tsx new file mode 100644 index 0000000..419cfc1 --- /dev/null +++ b/web/src/components/molecules/ModeOfPaymentsRadioGroup.tsx @@ -0,0 +1,46 @@ +import { + Radio, + FormLabel, + RadioGroup, + FormControl, + FormControlLabel, + SelectChangeEvent, +} from "@mui/material"; + +import { ModeOfPaymentEnum } from "../../utils/constants/ModeOfPaymentEnum"; +import { FC } from "react"; + +type Props = { + defaultMOP?: string; + onChange?: (e: SelectChangeEvent) => void; +}; + +const ModeOfPaymentsRadioGroup: FC = ({ defaultMOP, onChange }) => { + const modeOfPayments = Object.values(ModeOfPaymentEnum); + + return ( + + + Method of Payment * + + + {modeOfPayments.map((mop: string, index) => ( + } + /> + ))} + + + ); +}; + +export default ModeOfPaymentsRadioGroup; diff --git a/web/src/components/molecules/PersonInChargeSelectDropdown.tsx b/web/src/components/molecules/PersonInChargeSelectDropdown.tsx new file mode 100644 index 0000000..cfb30d2 --- /dev/null +++ b/web/src/components/molecules/PersonInChargeSelectDropdown.tsx @@ -0,0 +1,40 @@ +import React, { FC } from "react"; +import { + FormControl, + InputLabel, + Select, + MenuItem, + SelectChangeEvent, +} from "@mui/material"; +import { users } from "../organisms/UpdateJobDetailSection/hooks"; + +type Props = { + personInChargeId?: number; + onChange?: (e: SelectChangeEvent) => void; +}; + +const PersonInChargeSelectDropdown: FC = ({ + personInChargeId, + onChange, +}) => { + return ( + + Person in Charge * + + + ); +}; + +export default PersonInChargeSelectDropdown; diff --git a/web/src/components/molecules/TagsMultiSelectDropdown.tsx b/web/src/components/molecules/TagsMultiSelectDropdown.tsx new file mode 100644 index 0000000..1fea6a9 --- /dev/null +++ b/web/src/components/molecules/TagsMultiSelectDropdown.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { + SelectChangeEvent, + FormControl, + InputLabel, + Select, + OutlinedInput, + Box, + Chip, + MenuItem, +} from "@mui/material"; + +import { TagsEnum } from "../../utils/constants/TagsEnum"; + +const TagsMultiSelectDropdown + = () => { + const tagNames = Object.values(TagsEnum); + + const [tags, setTags] = React.useState([]); + + const handleChange = (event: SelectChangeEvent) => { + const value = event.target.value; + setTags(typeof value === "string" ? value.split(",") : value); + }; + + return ( +
+ + Tags * + + +
+ ); +}; + +export default TagsMultiSelectDropdown +; diff --git a/web/src/components/organisms/BreadCrumbsSection/index.tsx b/web/src/components/organisms/BreadCrumbsSection/index.tsx new file mode 100644 index 0000000..c308192 --- /dev/null +++ b/web/src/components/organisms/BreadCrumbsSection/index.tsx @@ -0,0 +1,14 @@ +import { FC } from "react"; +import { Breadcrumbs } from "@mui/material"; + +type Props = { + crumbs: JSX.Element[]; +}; + +export const BreadcrumbsSection: FC = ({ crumbs }): JSX.Element => { + return ( + <> + {crumbs} + + ); +}; diff --git a/web/src/components/organisms/JobCustomerSection.tsx b/web/src/components/organisms/JobCustomerSection.tsx new file mode 100644 index 0000000..8cbffdc --- /dev/null +++ b/web/src/components/organisms/JobCustomerSection.tsx @@ -0,0 +1,60 @@ +import { FC } from "react"; +import { Box, Typography, TextField } from "@mui/material"; + +const JobCustomerSection: FC = () => { + return ( + + Customer Registration + + + + + + + + + + + ); +}; + +export default JobCustomerSection; diff --git a/web/src/components/organisms/JobInformationSection.tsx b/web/src/components/organisms/JobInformationSection.tsx new file mode 100644 index 0000000..65ed3e1 --- /dev/null +++ b/web/src/components/organisms/JobInformationSection.tsx @@ -0,0 +1,50 @@ +import React, { FC } from "react"; +import { Box, TextField, Typography } from "@mui/material"; + +import ModeOfPaymentsRadioGroup from "../molecules/ModeOfPaymentsRadioGroup"; +import PersonInChargeSelectDropdown from "../molecules/PersonInChargeSelectDropdown"; +import TagsMultiSelectDropdown from "../molecules/TagsMultiSelectDropdown"; + +const JobInformationSection: FC = () => { + return ( + + Job Information + + + + + + + + + + + + + + + ); +}; + +export default JobInformationSection; diff --git a/web/src/components/organisms/JobWorkScheduleSection.tsx b/web/src/components/organisms/JobWorkScheduleSection.tsx new file mode 100644 index 0000000..39960e0 --- /dev/null +++ b/web/src/components/organisms/JobWorkScheduleSection.tsx @@ -0,0 +1,78 @@ +"use client"; + +import React, { FC } from "react"; +import { Moment } from "moment"; + +import { Box, Button, IconButton, Typography } from "@mui/material"; +import { CloseOutlined } from "@mui/icons-material"; +import { + DatePicker, + LocalizationProvider, + TimePicker, +} from "@mui/x-date-pickers"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; + +const JobWorkScheduleSection: FC = () => { + const [startDate, setStartDate] = React.useState(null); + const [startTime, setStartTime] = React.useState(null); + const [endDate, setEndDate] = React.useState(null); + const [endTime, setEndTime] = React.useState(null); + + const updateStartDate = (newStartDate: Moment | null): void => { + setStartDate(newStartDate); + }; + + const updateStartTime = (newStartTime: Moment | null): void => { + setStartTime(newStartTime); + }; + + const updateEndDate = (newEndDate: Moment | null) => { + setEndDate(newEndDate); + }; + + const updateEndTime = (newEndTime: Moment | null) => { + setEndTime(newEndTime); + }; + + return ( + + Work Schedule + + + + + + + + + + + + + + + + + + ); +}; + +export default JobWorkScheduleSection; diff --git a/web/src/components/organisms/SubmitButton/hooks.ts b/web/src/components/organisms/SubmitButton/hooks.ts new file mode 100644 index 0000000..7949fb9 --- /dev/null +++ b/web/src/components/organisms/SubmitButton/hooks.ts @@ -0,0 +1,10 @@ +import { FormValuesType } from "@/utils/interfaces"; + +export const handleSubmit = ( + formValues: FormValuesType, + buttonState: boolean, + setButtonState: (newButtonState: boolean) => void +) => { + setButtonState(!buttonState); + alert(JSON.stringify(formValues, null, 2)); +}; diff --git a/web/src/components/organisms/SubmitButton/index.tsx b/web/src/components/organisms/SubmitButton/index.tsx new file mode 100644 index 0000000..e8e9a61 --- /dev/null +++ b/web/src/components/organisms/SubmitButton/index.tsx @@ -0,0 +1,22 @@ +import { FC } from "react"; +import { handleSubmit } from "./hooks"; +import { Box, Button } from "@mui/material"; +import { useJobDetailContext } from "@/app/job/detail/context"; + +export const SubmitButton: FC = () => { + const { formValues, buttonState, setButtonState } = useJobDetailContext(); + + return ( + + + + ); +}; diff --git a/web/src/components/organisms/UpdateJobCustomerSection/hooks.tsx b/web/src/components/organisms/UpdateJobCustomerSection/hooks.tsx new file mode 100644 index 0000000..98a6c46 --- /dev/null +++ b/web/src/components/organisms/UpdateJobCustomerSection/hooks.tsx @@ -0,0 +1,52 @@ +import { CustomerFormType, FormValuesType } from "@/utils/interfaces"; +import { ChangeEvent, Dispatch, SetStateAction } from "react"; + +export const handleInputProps = (editEnabled?: boolean) => { + return !editEnabled ? { readOnly: true } : { readOnly: false }; +}; + +export const handleEdit = ( + editEnabled: boolean, + setEditEnabled: Dispatch> +) => { + setEditEnabled(!editEnabled); +}; + +export const handleInputChange = ( + event: ChangeEvent, + customerDetails: CustomerFormType, + setCustomerDetails: (initialFormValues: CustomerFormType) => void +) => { + const { name, value } = event.target; + setCustomerDetails({ + ...customerDetails, + [name]: value, + }); +}; + +export const handleSave = ( + state: boolean, + editEnabled: boolean, + setEditEnabled: Dispatch>, + customerDetails: CustomerFormType, + formValues: FormValuesType, + setFormValues: (newFormValue: FormValuesType) => void, + setButtonState: (newButtonState: boolean) => void +) => { + setButtonState(state); + setEditEnabled(!editEnabled); + setFormValues({ + ...formValues, + ["customer_registration"]: customerDetails, + }); +}; + +export const handleCancel = ( + editEnabled: boolean, + setEditEnabled: Dispatch>, + formValues: FormValuesType, + setCustomerDetails: (newFormValue: CustomerFormType) => void, +) => { + setEditEnabled(!editEnabled); + setCustomerDetails(formValues.customer_registration); +}; diff --git a/web/src/components/organisms/UpdateJobCustomerSection/index.tsx b/web/src/components/organisms/UpdateJobCustomerSection/index.tsx new file mode 100644 index 0000000..e0fb834 --- /dev/null +++ b/web/src/components/organisms/UpdateJobCustomerSection/index.tsx @@ -0,0 +1,165 @@ +import EditIcon from "@mui/icons-material/Edit"; +import React, { FC, useRef, useState, useEffect } from "react"; +import { Box, Typography, TextField, Button, Grid } from "@mui/material"; +import { + handleCancel, + handleInputChange, + handleInputProps, + handleSave, +} from "./hooks"; +import { useJobDetailContext } from "@/app/job/detail/context"; + +const JobCustomerSection: FC = () => { + const firstNameRef = useRef(null); + const [editEnabled, setEditEnabled] = useState(false); + + const { + formValues, + setFormValues, + setButtonState, + customerDetails, + setCustomerDetails, + } = useJobDetailContext(); + + useEffect(() => { + if (editEnabled && firstNameRef.current) { + firstNameRef.current.focus(); + } + }, [editEnabled]); + + return ( + + + Customer Registration + {!editEnabled && ( + setEditEnabled(!editEnabled)} + /> + )} + + + + + handleInputChange(e, customerDetails, setCustomerDetails) + } + /> + + handleInputChange(e, customerDetails, setCustomerDetails) + } + /> + + + + handleInputChange(e, customerDetails, setCustomerDetails) + } + /> + + handleInputChange(e, customerDetails, setCustomerDetails) + } + /> + + + handleInputChange(e, customerDetails, setCustomerDetails) + } + /> + {editEnabled && ( + + + + + + + + + )} + + ); +}; + +export default JobCustomerSection; diff --git a/web/src/components/organisms/UpdateJobDetailSection/hooks.tsx b/web/src/components/organisms/UpdateJobDetailSection/hooks.tsx new file mode 100644 index 0000000..e45a71a --- /dev/null +++ b/web/src/components/organisms/UpdateJobDetailSection/hooks.tsx @@ -0,0 +1,119 @@ +import { FormValuesType, InformationFormType } from "@/utils/interfaces"; +import { ChangeEvent, Dispatch, SetStateAction } from "react"; +import { PICDummyValues } from "@/utils/constants/PICDummyValues"; +import { Chip, SelectChangeEvent, Typography } from "@mui/material"; + +export const users = PICDummyValues; + +export const handlePersonInChargeChange = ( + e: SelectChangeEvent, + formValues: InformationFormType, + setFormValues: (JobInformationDummy: InformationFormType) => void +) => { + const { value } = e.target; + const userData = users.find((user) => user.id === Number(value)); + + if (userData !== undefined) { + setFormValues({ + ...formValues, + ["personInCharge"]: userData, + }); + } +}; + +export const handleMOPChange = ( + e: SelectChangeEvent, + formValues: InformationFormType, + setFormValues: (JobInformationDummy: InformationFormType) => void +) => { + const { value } = e.target; + setFormValues({ + ...formValues, + ["modeOfPayment"]: value, + }); +}; + +export const handleTagChips = ( + editEnabled: boolean, + formValues: InformationFormType, + setFormValues: (JobInformationDummy: InformationFormType) => void +): JSX.Element[] => { + const { tags } = formValues; + const handleDelete = ( + index: number, + tagArray: string[], + setFormValues: (JobInformationDummy: InformationFormType) => void + ) => { + const updatedItems = [...tagArray]; + updatedItems.splice(index, 1); + return setFormValues({ + ...formValues, + ["tags"]: updatedItems, + }); + }; + + return tags.map((item, index) => { + return ( + {item}} + onDelete={ + editEnabled + ? () => handleDelete(index, tags, setFormValues) + : undefined + } + /> + ); + }); +}; + +export const handleInputProps = (editEnabled?: boolean) => { + return !editEnabled ? { readOnly: true } : { readOnly: false }; +}; + +export const handleEdit = ( + editEnabled: boolean, + setEditEnabled: Dispatch> +) => { + return setEditEnabled(!editEnabled); +}; + +export const handleInputChange = ( + event: ChangeEvent, + informationDetails: InformationFormType, + setInformationDetails: (JobInformationDummy: InformationFormType) => void +): void => { + const { name, value } = event.target; + setInformationDetails({ + ...informationDetails, + [name]: value, + }); +}; + +export const handleSave = ( + state: boolean, + editEnabled: boolean, + setEditEnabled: Dispatch>, + informationDetails: InformationFormType, + formValues: FormValuesType, + setFormValues: (newFormValue: FormValuesType) => void, + setButtonState: (newButtonState: boolean) => void +) => { + setButtonState(state); + setEditEnabled(!editEnabled); + setFormValues({ + ...formValues, + ["job_information"]: informationDetails, + }); +}; + +export const handleCancel = ( + editEnabled: boolean, + setEditEnabled: Dispatch>, + formValues: FormValuesType, + setInformationDetails: (newFormValue: InformationFormType) => void +) => { + setEditEnabled(!editEnabled); + setInformationDetails(formValues.job_information); +}; diff --git a/web/src/components/organisms/UpdateJobDetailSection/index.tsx b/web/src/components/organisms/UpdateJobDetailSection/index.tsx new file mode 100644 index 0000000..5dca921 --- /dev/null +++ b/web/src/components/organisms/UpdateJobDetailSection/index.tsx @@ -0,0 +1,296 @@ +import React, { FC, useEffect, useRef, useState } from "react"; +import { + Box, + Grid, + Table, + Button, + TableRow, + TableBody, + TableCell, + TextField, + Typography, +} from "@mui/material"; +import EditIcon from "@mui/icons-material/Edit"; + +import ModeOfPaymentsRadioGroup from "../../molecules/ModeOfPaymentsRadioGroup"; +import PersonInChargeSelectDropdown from "../../molecules/PersonInChargeSelectDropdown"; +import { + handleEdit, + handleTagChips, + handleMOPChange, + handleInputProps, + handleInputChange, + handlePersonInChargeChange, + handleSave, + handleCancel, +} from "./hooks"; +import { useJobDetailContext } from "@/app/job/detail/context"; + +const JobDetailSection: FC = () => { + const jobTitle = useRef(null); + const [editEnabled, setEditEnabled] = useState(false); + const { + informationDetails, + setInformationDetails, + formValues, + setFormValues, + setButtonState, + } = useJobDetailContext(); + + useEffect(() => { + if (editEnabled && jobTitle.current) { + jobTitle.current.focus(); + } + }, [editEnabled]); + + return ( + + + Job Information + {!editEnabled && ( + handleEdit(editEnabled, setEditEnabled)} + /> + )} + + + 1, borderColor: () => "light" }}> + + {/* Job Title */} + + "background-primary", + width: () => "25%", + }} + > + Job Title + + + {!editEnabled ? ( + + {informationDetails.jobTitle} + + ) : ( + + handleInputChange( + e, + informationDetails, + setInformationDetails + ) + } + /> + )} + + + {/* Job Type */} + + "background-primary", + width: () => "25%", + }} + > + Job Type + + + {!editEnabled ? ( + + {informationDetails.jobType} + + ) : ( + + handleInputChange( + e, + informationDetails, + setInformationDetails + ) + } + /> + )} + + + {/* Person In Charge */} + + "background-primary", + width: () => "25%", + }} + > + Person in charge + + + {!editEnabled ? ( + + {informationDetails.personInCharge?.firstName} + + ) : ( + informationDetails.personInCharge !== undefined && ( + + handlePersonInChargeChange( + e, + informationDetails, + setInformationDetails + ) + } + /> + ) + )} + + + {/* Tags */} + + "background-primary", + width: () => "25%", + }} + > + Tags + + + {handleTagChips( + editEnabled, + informationDetails, + setInformationDetails + )} + + + {/* Remarks */} + + "background-primary", + width: () => "25%", + }} + > + Remarks + + + {!editEnabled ? ( + + {informationDetails.remarks} + + ) : ( + + handleInputChange( + e, + informationDetails, + setInformationDetails + ) + } + /> + )} + + + {/* Mod */} + {!editEnabled && ( + + "background-primary", + width: () => "25%", + }} + > + Mode of Payment + + + + {informationDetails.modeOfPayment} + + + + )} + +
+ + {editEnabled && ( + <> + + handleMOPChange(e, informationDetails, setInformationDetails) + } + /> + + + + + + + + + + )} +
+ ); +}; + +export default JobDetailSection; diff --git a/web/src/components/organisms/UpdateJobScheduleSection/hooks.tsx b/web/src/components/organisms/UpdateJobScheduleSection/hooks.tsx new file mode 100644 index 0000000..ee512ba --- /dev/null +++ b/web/src/components/organisms/UpdateJobScheduleSection/hooks.tsx @@ -0,0 +1,55 @@ +import { Moment } from "moment"; +import { Dispatch, SetStateAction } from "react"; +import { FormValuesType, ScheduleFormType } from "@/utils/interfaces"; + +export const setValueForm = ( + e: Moment | null, + name: string, + scheduleDetail: ScheduleFormType[], + setScheduleDetails: (newFormValue: ScheduleFormType[]) => void, + index: number +) => { + const data = e?.format("YYYY-MM-DDTHH:mm:ss[Z]"); + if (data !== undefined) { + const updatedScheduleDetails = [...scheduleDetail]; + updatedScheduleDetails[index] = { + ...updatedScheduleDetails[index], + [name]: data, + }; + setScheduleDetails(updatedScheduleDetails); + } +}; + +export const handleEdit = ( + editEnabled: boolean, + setEditEnabled: Dispatch> +) => { + setEditEnabled(!editEnabled); +}; + +export const handleSave = ( + state: boolean, + editEnabled: boolean, + setEditEnabled: Dispatch>, + scheduleDetails: ScheduleFormType[], + formValues: FormValuesType, + setFormValues: (newFormValue: FormValuesType) => void, + setButtonState: (newButtonState: boolean) => void +) => { + setButtonState(state); + setEditEnabled(!editEnabled); + setFormValues({ + ...formValues, + ["work_schedule"]: scheduleDetails, + }); +}; + +export const handleCancel = ( + editEnabled: boolean, + setEditEnabled: Dispatch>, + formValues: FormValuesType, + setScheduleDetails: (newFormValue: ScheduleFormType[]) => void +) => { + setEditEnabled(!editEnabled); + setScheduleDetails(formValues.work_schedule); +}; diff --git a/web/src/components/organisms/UpdateJobScheduleSection/index.tsx b/web/src/components/organisms/UpdateJobScheduleSection/index.tsx new file mode 100644 index 0000000..930d030 --- /dev/null +++ b/web/src/components/organisms/UpdateJobScheduleSection/index.tsx @@ -0,0 +1,180 @@ +import moment from "moment"; +import React, { FC, useEffect, useRef, useState } from "react"; + +import EditIcon from "@mui/icons-material/Edit"; +import { CloseOutlined } from "@mui/icons-material"; +import { + DatePicker, + TimePicker, + LocalizationProvider, +} from "@mui/x-date-pickers"; +import { useJobDetailContext } from "@/app/job/detail/context"; +import { handleCancel, handleEdit, handleSave, setValueForm } from "./hooks"; +import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment"; +import { Box, Button, Grid, IconButton, Typography } from "@mui/material"; + +const JobWorkScheduleSection: FC = () => { + const startDateRef = useRef(null); + const [editEnabled, setEditEnabled] = useState(false); + + const { + scheduleDetails, + setScheduleDetails, + formValues, + setFormValues, + setButtonState, + } = useJobDetailContext(); + + useEffect(() => { + if (editEnabled && startDateRef.current) { + startDateRef.current.focus(); + } + }, [editEnabled]); + + return ( + + + Work Schedule + {!editEnabled && ( + handleEdit(editEnabled, setEditEnabled)} + /> + )} + + + {scheduleDetails.map((scheduleDetail, index) => { + return ( + + + + setValueForm( + e, + "startDate", + scheduleDetails, + setScheduleDetails, + index + ) + } + inputRef={startDateRef} + /> + + setValueForm( + e, + "startTime", + scheduleDetails, + setScheduleDetails, + index + ) + } + /> + + setValueForm( + e, + "endDate", + scheduleDetails, + setScheduleDetails, + index + ) + } + /> + + setValueForm( + e, + "endTime", + scheduleDetails, + setScheduleDetails, + index + ) + } + /> + + {editEnabled && ( + + + + )} + + + ); + })} + + {editEnabled && ( + <> + + + + + + + + + + )} + + ); +}; + +export default JobWorkScheduleSection; diff --git a/web/src/utils/constants/FormInitialDummyValues.ts b/web/src/utils/constants/FormInitialDummyValues.ts new file mode 100644 index 0000000..2a19854 --- /dev/null +++ b/web/src/utils/constants/FormInitialDummyValues.ts @@ -0,0 +1,31 @@ +export const FormValues = { + customer_registration: { + firstName: "First Name", + lastName: "Last Name", + contact: "1234567890", + email: "sample@g.com", + address: "123 St", + }, + job_information: { + jobTitle: "Fix leaky faucet", + jobType: "Plumbing", + personInCharge: { id: 2, firstName: "Jane" }, + tags: ["Tag A", "Tag B", "Tag C"], + remarks: "A remark", + modeOfPayment: "Card", + }, + work_schedule: [ + { + startDate: "2023-09-13T00:10:00Z", + startTime: "2025-10-01T00:00:00Z", + endTime: "2023-09-13T00:10:00Z", + endDate: "2023-09-13T00:10:00Z", + }, + { + startDate: "2023-09-13T00:10:00Z", + startTime: "2025-10-01T00:00:00Z", + endTime: "2023-09-13T00:10:00Z", + endDate: "2023-09-13T00:10:00Z", + }, + ], +}; diff --git a/web/src/utils/constants/ModeOfPaymentEnum.ts b/web/src/utils/constants/ModeOfPaymentEnum.ts new file mode 100644 index 0000000..e8c3e5a --- /dev/null +++ b/web/src/utils/constants/ModeOfPaymentEnum.ts @@ -0,0 +1,5 @@ +export enum ModeOfPaymentEnum { + CASH = "Cash", + CARD = "Card", + BANK_TRANSFER = "Bank Transfer" +} diff --git a/web/src/utils/constants/PICDummyValues.ts b/web/src/utils/constants/PICDummyValues.ts new file mode 100644 index 0000000..49fad22 --- /dev/null +++ b/web/src/utils/constants/PICDummyValues.ts @@ -0,0 +1,5 @@ +export const PICDummyValues = [ + { id: 1, firstName: "John" }, + { id: 2, firstName: "Jane" }, + { id: 3, firstName: "Jack" }, +]; diff --git a/web/src/utils/constants/TagsEnum.ts b/web/src/utils/constants/TagsEnum.ts new file mode 100644 index 0000000..7d380b7 --- /dev/null +++ b/web/src/utils/constants/TagsEnum.ts @@ -0,0 +1,5 @@ +export enum TagsEnum { + TAG_A = "TAG_A", + TAG_B = "TAG_B", + TAG_C = "TAG_C", +} diff --git a/web/src/utils/interfaces/index.tsx b/web/src/utils/interfaces/index.tsx new file mode 100644 index 0000000..7801d18 --- /dev/null +++ b/web/src/utils/interfaces/index.tsx @@ -0,0 +1,48 @@ +export interface InformationFormType { + jobTitle: string; + jobType: string; + personInCharge: { id: number; firstName: string }; + tags: string[]; + remarks: string; + modeOfPayment: string; +} + +export interface CustomerFormType { + firstName: string; + lastName: string; + contact: string; + email: string; + address: string; +} + +export interface ScheduleFormType { + startDate: string; + startTime: string; + endDate: string; + endTime: string; +} +[]; + +export interface FormValuesType { + customer_registration: { + firstName: string; + lastName: string; + contact: string; + email: string; + address: string; + }; + job_information: { + jobTitle: string; + jobType: string; + personInCharge: { id: number; firstName: string }; + tags: string[]; + remarks: string; + modeOfPayment: string; + }; + work_schedule: { + startDate: string; + startTime: string; + endDate: string; + endTime: string; + }[]; +} diff --git a/web/yarn.lock b/web/yarn.lock index 8662640..0737179 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -277,6 +277,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.22.11": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -756,6 +763,29 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.14.7": + version "5.14.7" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.7.tgz#3677bcabe032f1185e151f57d8c1a166df3ae0a1" + integrity sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw== + dependencies: + "@babel/runtime" "^7.22.10" + "@types/prop-types" "^15.7.5" + "@types/react-is" "^18.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + +"@mui/x-date-pickers@^6.12.1": + version "6.12.1" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.12.1.tgz#ffd30b61ee58267ffdb4f2d8e0b2181c4ac842e8" + integrity sha512-euMM7KNbqoOgIdf5P8T8KG74qqbpDvLp5k8yEVMx1zHeSiSZGofKpQrDFAMxjJRnJ0x1aJLIhFEc46O70fSAlA== + dependencies: + "@babel/runtime" "^7.22.11" + "@mui/utils" "^5.14.7" + "@types/react-transition-group" "^4.4.6" + clsx "^2.0.0" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@next/env@13.4.12": version "13.4.12" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.12.tgz#0b88115ab817f178bf9dc0c5e7b367277595b58d" @@ -3270,6 +3300,11 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -3607,6 +3642,11 @@ react-dom@18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-hook-form@^7.46.1: + version "7.46.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.46.1.tgz#39347dbff19d980cb41087ac32a57abdc6045bb3" + integrity sha512-0GfI31LRTBd5tqbXMGXT1Rdsv3rnvy0FjEk8Gn9/4tp6+s77T7DPZuGEpBRXOauL+NhyGT5iaXzdIM2R6F/E+w== + react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"