diff --git a/web/src/app/job/context.ts b/web/src/app/job/context.ts index cc54b7d..92fe1c4 100644 --- a/web/src/app/job/context.ts +++ b/web/src/app/job/context.ts @@ -1,4 +1,5 @@ -import { JobTable } from '@/utils/types/job'; +import { JobQuery, JobTable } from '@/utils/types/job'; import { createContext } from 'react'; export const JobListContext = createContext(undefined); +export const JobQueryContext = createContext(undefined); diff --git a/web/src/app/job/hooks.ts b/web/src/app/job/hooks.ts index b9ed0cb..28d30e7 100644 --- a/web/src/app/job/hooks.ts +++ b/web/src/app/job/hooks.ts @@ -1,8 +1,9 @@ import { axiosInstance } from '@/utils/services/axios'; -import { JobSchema, JobTable, JobTableRow } from '@/utils/types/job'; +import { JobQuery, JobSchema, JobTable, JobTableRow } from '@/utils/types/job'; import { ScheduleSchema } from '@/utils/types/schedule'; +import { Moment } from 'moment'; import { useContext, useEffect, useState } from 'react'; -import { JobListContext } from './context'; +import { JobListContext, JobQueryContext } from './context'; export const useJobListContext = (): JobTable => { const jobs = useContext(JobListContext); @@ -14,6 +15,16 @@ export const useJobListContext = (): JobTable => { return jobs; }; +export const useJobQueryContext = (): JobQuery => { + const query = useContext(JobQueryContext); + + if (query === undefined) { + throw new Error('Missing JobQueryContext'); + } + + return query; +}; + export const useHooks = () => { const [page, setPage] = useState(1); const [perPage] = useState(12); @@ -23,13 +34,36 @@ export const useHooks = () => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); + const [isFilter, setIsFilter] = useState(false); + const [tag, setTag] = useState(''); + const [status, setStatus] = useState(''); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + + const params: { page: number, perPage: number, tag?: string, status?: string, startDate?: string, + endDate?: string } = { + page, perPage, + }; + + if(isFilter) { + if (tag) { + params.tag = tag; + } + + if (status) { + params.status = status; + } + + if (startDate && endDate) { + params.startDate = startDate.format("MM-DD-YYYY"); + params.endDate = endDate.format("MM-DD-YYYY"); + } + } + useEffect(() => { axiosInstance .get('/jobs', { - params: { - page, - perPage - } + params }) .then((response) => { setCount(response?.data.count); @@ -42,7 +76,7 @@ export const useHooks = () => { setIsLoading(false); setError('Something went wrong.'); }); - }, [page, perPage]); + }, [page, perPage, tag, status, startDate, endDate, isFilter]); return { jobs, @@ -52,6 +86,16 @@ export const useHooks = () => { isLoading, error, setPage, + tag, + setTag, + status, + setStatus, + startDate, + setStartDate, + endDate, + setEndDate, + isFilter, + setIsFilter }; }; @@ -81,7 +125,7 @@ const formatSchedules = (schedules: ScheduleSchema[]): string[] => { return scheduleData; }; -const formatEnum = (value: string): string => { +export const formatEnum = (value: string): string => { let words = value.split('_'); words = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); return words.join(' '); diff --git a/web/src/app/job/page.tsx b/web/src/app/job/page.tsx index 98f910d..05f1d94 100644 --- a/web/src/app/job/page.tsx +++ b/web/src/app/job/page.tsx @@ -7,61 +7,92 @@ import SearchFilterHeader from '@/components/organisms/SearchFilterHeader'; import { JobColumns } from '@/utils/constants/jobTableData'; import { Grid, Typography } from '@mui/material'; import { Fragment } from 'react'; -import { JobListContext } from './context'; +import { JobListContext, JobQueryContext } from './context'; import { useHooks } from './hooks'; const JobList = (): JSX.Element => { - const { page, jobs, count, pageCount, setPage, isLoading, error } = - useHooks(); + const { + page, + jobs, + count, + pageCount, + setPage, + isLoading, + error, + tag, + setTag, + status, + setStatus, + startDate, + setStartDate, + endDate, + setEndDate, + isFilter, + setIsFilter + } = useHooks(); return (
- {isLoading ? ( - - ) : error ? ( - - ) : ( - - - - - {!count ? ( - - - No jobs found - + + {isLoading ? ( + + ) : error ? ( + + ) : ( + + + - ) : ( - - - - - - + {!count ? ( + + + No jobs found + - - )} - - )} + ) : ( + + + + + + + + + )} + + )} +
); diff --git a/web/src/components/molecules/CreatedDateRangeFilter/hooks.ts b/web/src/components/molecules/CreatedDateRangeFilter/hooks.ts index b0ab4b4..ec5db0f 100644 --- a/web/src/components/molecules/CreatedDateRangeFilter/hooks.ts +++ b/web/src/components/molecules/CreatedDateRangeFilter/hooks.ts @@ -1,30 +1,48 @@ -import moment, { type Moment } from 'moment'; +import { useJobQueryContext } from '@/app/job/hooks'; +import { type Moment } from 'moment'; import { useEffect, useState } from 'react'; export const useHooks = () => { - const [startDate, setStartDate] = useState(moment().startOf('month')); - const [endDate, setEndDate] = useState(moment()); - const [isInvalidDate, setIsInvalidDate] = useState(false); + const { setStartDate, setEndDate } = useJobQueryContext(); + const [initialStartDate, setInitialStartDate] = useState(null); + const [initialEndDate, setInitialEndDate] = useState(null); + const [error, setError] = useState(''); const handleStartDateChange = (date: Moment | null): void => { - setStartDate(date); + setInitialStartDate(date); }; const handleEndDateChange = (date: Moment | null): void => { - setEndDate(date); + setInitialEndDate(date); }; useEffect(() => { - if (startDate && endDate) { - setIsInvalidDate(endDate <= startDate); + if (initialStartDate && !initialEndDate || initialEndDate && !initialStartDate) { + setError('Both start and end dates must be set'); } - }, [startDate, endDate]); + + if(!initialStartDate && !initialEndDate) { + setError(''); + setStartDate(null); + setEndDate(null); + } + + if (initialStartDate && initialEndDate) { + if (initialEndDate < initialStartDate) { + setError('Please set a vaild date'); + } else { + setError(''); + setStartDate(initialStartDate); + setEndDate(initialEndDate); + } + } + }, [initialStartDate, initialEndDate]); return { - startDate, + error, + initialStartDate, handleStartDateChange, - endDate, + initialEndDate, handleEndDateChange, - isInvalidDate }; }; diff --git a/web/src/components/molecules/CreatedDateRangeFilter/index.tsx b/web/src/components/molecules/CreatedDateRangeFilter/index.tsx index 9746682..ebd7697 100644 --- a/web/src/components/molecules/CreatedDateRangeFilter/index.tsx +++ b/web/src/components/molecules/CreatedDateRangeFilter/index.tsx @@ -8,11 +8,11 @@ import { useHooks } from './hooks'; const CreateDateRangeFilter = () => { const { - startDate, + error, + initialStartDate, handleStartDateChange, - endDate, - handleEndDateChange, - isInvalidDate + initialEndDate, + handleEndDateChange } = useHooks(); return ( @@ -21,7 +21,7 @@ const CreateDateRangeFilter = () => { { } }} slotProps={{ + actionBar: { + actions: ['clear'] + }, textField: { size: 'small', color: 'secondary', id: 'created-start', name: 'created-start', - error: isInvalidDate, - helperText: isInvalidDate - ? 'Please set a valid date' - : '' + error: !!error, + helperText: error } }} /> - {' - '} { } }} slotProps={{ + actionBar: { + actions: ['clear'] + }, textField: { size: 'small', color: 'secondary', id: 'created-end', name: 'created-end', - error: isInvalidDate, - helperText: isInvalidDate - ? 'Please set a valid date.' - : '' + error: !!error, + helperText: error } }} /> diff --git a/web/src/components/molecules/EstimationStatusFilter/hooks.ts b/web/src/components/molecules/EstimationStatusFilter/hooks.ts index 9c1499c..8bdcc38 100644 --- a/web/src/components/molecules/EstimationStatusFilter/hooks.ts +++ b/web/src/components/molecules/EstimationStatusFilter/hooks.ts @@ -1,15 +1,24 @@ +import { formatEnum, useJobQueryContext } from '@/app/job/hooks'; +import { EstimationStatusEnum } from '@/utils/constants/estimationStatusEnum'; import { SelectChangeEvent } from '@mui/material'; -import { useState } from 'react'; export const useHooks = () => { - const [status, setStatus] = useState(''); + const { status, setStatus } = useJobQueryContext(); const handleChange = (e: SelectChangeEvent) => { setStatus(e.target.value); }; + const statusOptions = Object.keys(EstimationStatusEnum).map((key) => { + return { + value: EstimationStatusEnum[key as keyof typeof EstimationStatusEnum], + name: formatEnum(key) + } + }); + return { status, + statusOptions, handleChange }; }; diff --git a/web/src/components/molecules/EstimationStatusFilter/index.tsx b/web/src/components/molecules/EstimationStatusFilter/index.tsx index 99188dc..9553d24 100644 --- a/web/src/components/molecules/EstimationStatusFilter/index.tsx +++ b/web/src/components/molecules/EstimationStatusFilter/index.tsx @@ -1,10 +1,9 @@ import styles from '@/styles/Filter.module.css'; -import { EstimationStatus } from '@/utils/constants/estimationStatus'; import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; import { useHooks } from './hooks'; const EstimationStatusFilter = () => { - const { status, handleChange } = useHooks(); + const { status, statusOptions, handleChange } = useHooks(); return ( @@ -20,9 +19,12 @@ const EstimationStatusFilter = () => { color='secondary' onChange={handleChange} className={styles.input}> - {EstimationStatus.map((status) => ( - - {status.status} + + None + + {statusOptions.map((status, key) => ( + + {status.name} ))} diff --git a/web/src/components/molecules/TagFilter/hooks.ts b/web/src/components/molecules/TagFilter/hooks.ts index e835a95..eb706cd 100644 --- a/web/src/components/molecules/TagFilter/hooks.ts +++ b/web/src/components/molecules/TagFilter/hooks.ts @@ -1,15 +1,25 @@ +import { formatEnum, useJobQueryContext } from '@/app/job/hooks'; +import { TagsEnum } from '@/utils/constants/tagsEnum'; + import { SelectChangeEvent } from '@mui/material'; -import { useState } from 'react'; export const useHooks = () => { - const [tag, setTag] = useState(''); + const { tag, setTag } = useJobQueryContext(); const handleChange = (e: SelectChangeEvent) => { setTag(e.target.value); }; + + const tagOptions = Object.keys(TagsEnum).map((key) => { + return { + value: TagsEnum[key as keyof typeof TagsEnum], + name: formatEnum(key) + } + }); return { tag, + tagOptions, handleChange }; }; diff --git a/web/src/components/molecules/TagFilter/index.tsx b/web/src/components/molecules/TagFilter/index.tsx index d12fed7..6594b36 100644 --- a/web/src/components/molecules/TagFilter/index.tsx +++ b/web/src/components/molecules/TagFilter/index.tsx @@ -1,10 +1,9 @@ import styles from '@/styles/Filter.module.css'; -import { Tags } from '@/utils/constants/tags'; import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; import { useHooks } from './hooks'; const TagFilter = () => { - const { tag, handleChange } = useHooks(); + const { tag, tagOptions, handleChange } = useHooks(); return ( @@ -20,8 +19,11 @@ const TagFilter = () => { color='secondary' onChange={handleChange} className={styles.input}> - {Tags.map((tag) => ( - + + None + + {tagOptions.map((tag, key) => ( + {tag.name} ))} diff --git a/web/src/components/organisms/SearchFilterHeader/hooks.ts b/web/src/components/organisms/SearchFilterHeader/hooks.ts index bfad85f..d499f3c 100644 --- a/web/src/components/organisms/SearchFilterHeader/hooks.ts +++ b/web/src/components/organisms/SearchFilterHeader/hooks.ts @@ -1,10 +1,13 @@ +import { useJobQueryContext } from '@/app/job/hooks'; import { useState } from 'react'; export const useHooks = () => { + const { isFilter, setIsFilter } = useJobQueryContext(); const [isExpanded, setIsExpanded] = useState(false); const toggleFilters = () => { setIsExpanded(!isExpanded); + setIsFilter(!isFilter); }; return { diff --git a/web/src/utils/constants/estimationStatus.ts b/web/src/utils/constants/estimationStatus.ts deleted file mode 100644 index cc2b981..0000000 --- a/web/src/utils/constants/estimationStatus.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const EstimationStatus = [ - { id: 1, status: 'Not yet Created' }, - { id: 2, status: 'Making' }, - { id: 3, status: 'Approved' }, - { id: 4, status: 'Sent to Customer' }, - { id: 5, status: 'Closed' } -]; diff --git a/web/src/utils/constants/estimationStatusEnum.ts b/web/src/utils/constants/estimationStatusEnum.ts new file mode 100644 index 0000000..5493ce0 --- /dev/null +++ b/web/src/utils/constants/estimationStatusEnum.ts @@ -0,0 +1,8 @@ + +export enum EstimationStatusEnum { + NOT_YET_CREATED = "NOT_YET_CREATED", + MAKING = "MAKING", + APPROVED = "APPROVED", + SENT_TO_CUSTOMER = "SENT_TO_CUSTOMER", + CLOSED = "CLOSED", +} diff --git a/web/src/utils/constants/tags.ts b/web/src/utils/constants/tags.ts deleted file mode 100644 index f021459..0000000 --- a/web/src/utils/constants/tags.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const Tags = [ - { id: 1, value: 'TAG_A', name: 'Tag A' }, - { id: 2, value: 'TAG_B', name: 'Tag B' }, - { id: 3, value: 'TAG_C', name: 'Tag C' } -]; 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/types/job.ts b/web/src/utils/types/job.ts index 33eee65..8cd0af4 100644 --- a/web/src/utils/types/job.ts +++ b/web/src/utils/types/job.ts @@ -1,3 +1,4 @@ +import { Moment } from 'moment'; import { CustomerSchema } from './customer'; import { EstimationSchema } from './estimation'; import { ScheduleSchema } from './schedule'; @@ -52,3 +53,16 @@ export interface JobListType { jobs: JobSchema[]; count: number } + +export interface JobQuery { + tag: string; + setTag: (value: string) => void; + status: string; + setStatus: (value: string) => void; + startDate: Moment | null; + setStartDate: (date: Moment | null) => void; + endDate: Moment | null; + setEndDate: (date: Moment | null) => void; + isFilter: boolean; + setIsFilter: (value: boolean) => void; +}