From ac3107a71a57c9c98bfd8772765fcf5df1dc7e3f Mon Sep 17 00:00:00 2001 From: nicoleamber Date: Wed, 6 Sep 2023 18:10:17 +0800 Subject: [PATCH] Job List Page --- web/src/app/page.tsx | 270 +++++++++++++++++- web/src/assets/icons/FilterCog.tsx | 20 ++ web/src/assets/icons/FilterRemove.tsx | 20 ++ web/src/components/atoms/Pagination.tsx | 33 +++ web/src/components/atoms/StatusChip.tsx | 30 ++ .../molecules/CreatedDateRangeFilter.tsx | 48 ++++ .../molecules/EstimationStatusFilter.tsx | 50 ++++ web/src/components/molecules/NavBarItem.tsx | 6 + web/src/components/molecules/SearchBar.tsx | 38 +++ web/src/components/molecules/TagFilter.tsx | 48 ++++ web/src/components/organisms/JobListTable.tsx | 151 ++++++++++ .../organisms/SearchFilterHeader.tsx | 68 +++++ web/src/utils/constants/navbarMenu.ts | 6 +- web/styles/Filter.module.css | 36 +++ 14 files changed, 812 insertions(+), 12 deletions(-) create mode 100644 web/src/assets/icons/FilterCog.tsx create mode 100644 web/src/assets/icons/FilterRemove.tsx create mode 100644 web/src/components/atoms/Pagination.tsx create mode 100644 web/src/components/atoms/StatusChip.tsx create mode 100644 web/src/components/molecules/CreatedDateRangeFilter.tsx create mode 100644 web/src/components/molecules/EstimationStatusFilter.tsx create mode 100644 web/src/components/molecules/SearchBar.tsx create mode 100644 web/src/components/molecules/TagFilter.tsx create mode 100644 web/src/components/organisms/JobListTable.tsx create mode 100644 web/src/components/organisms/SearchFilterHeader.tsx create mode 100644 web/styles/Filter.module.css diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 6c76689..ed4cf51 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -1,11 +1,263 @@ -import { Box, Typography } from "@mui/material"; +'use client'; -export default function Home() { - return ( -
- - Content goes here - -
- ) +import Pagination from '@/components/atoms/Pagination'; +import JobListTable from '@/components/organisms/JobListTable'; +import SearchFilterHeader from '@/components/organisms/SearchFilterHeader'; +import { Grid } from '@mui/material'; +import { useState } from 'react'; + +export interface Column { + key: string; + label: string; + width?: number; +} + +interface Estimation { + status: string; + cost: number; +} + +export interface JobData { + id: number; + title: string; + customer: string; + tags: Array; + schedules: Array; + estimation: Estimation; + personInCharge: string; + pipelinePhase: string; + createdAt: string; + [key: string]: string | number | string[] | Estimation; } + +const columns: Column[] = [ + { key: 'title', label: 'Job Title', width: 200 }, + { key: 'customer', label: 'Customer Name', width: 170 }, + { key: 'tags', label: 'Tags', width: 160 }, + { key: 'schedules', label: 'Work Schedule', width: 200 }, + { key: 'estimationStatus', label: 'Estimation Status', width: 180 }, + { key: 'personInCharge', label: 'Person in Charge', width: 170 }, + { key: 'pipelinePhase', label: 'Pipeline Phase', width: 150 }, + { key: 'cost', label: 'Cost', width: 120 }, + { key: 'createdAt', label: 'Created At', width: 120 } +]; + +export default function JobList() { + const [page, setPage] = useState(1); + + const handlePageChange = (value: number) => { + setPage(value); + }; + return ( +
+ + + + + + + + + + + +
+ ); +} + +const data: JobData[] = [ + { + id: 1, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A', 'Tag B'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Approved', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 2, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A', 'Tag B', 'Tag C', 'Tag D', 'Tag E'], + schedules: ['5-25-2023 5:00 - 15:00', '5-26-2023 5:00 - 15:00'], + estimation: { + status: 'Making', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 3, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Sent to Customer', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 4, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A', 'Tag B'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Closed', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 5, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A', 'Tag B'], + schedules: [ + '5-25-2023 5:00 - 15:00', + '5-26-2023 5:00 - 15:00', + '5-26-2023 5:00 - 15:00' + ], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 6, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 7, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 8, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 9, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + }, + { + id: 10, + title: 'New Summit', + customer: 'John Doe', + tags: ['Tag A'], + schedules: ['5-25-2023 5:00 - 15:00'], + estimation: { + status: 'Not yet Created', + cost: 650.0 + }, + personInCharge: 'Michael Murry', + pipelinePhase: 'Delivery', + createdAt: new Date('5-2-2023').toLocaleDateString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) + } +]; diff --git a/web/src/assets/icons/FilterCog.tsx b/web/src/assets/icons/FilterCog.tsx new file mode 100644 index 0000000..4d0ae75 --- /dev/null +++ b/web/src/assets/icons/FilterCog.tsx @@ -0,0 +1,20 @@ +import { SvgIcon } from '@mui/material'; + +interface Props { + style?: string; +} + +export const FilterCog = ({ style = '' }: Props) => { + return ( + + + + + + ); +}; diff --git a/web/src/assets/icons/FilterRemove.tsx b/web/src/assets/icons/FilterRemove.tsx new file mode 100644 index 0000000..8b40a87 --- /dev/null +++ b/web/src/assets/icons/FilterRemove.tsx @@ -0,0 +1,20 @@ +import { SvgIcon } from '@mui/material'; + +interface Props { + style?: string; +} + +export const FilterRemove = ({ style = '' }: Props) => { + return ( + + + + + + ); +}; diff --git a/web/src/components/atoms/Pagination.tsx b/web/src/components/atoms/Pagination.tsx new file mode 100644 index 0000000..ed1759a --- /dev/null +++ b/web/src/components/atoms/Pagination.tsx @@ -0,0 +1,33 @@ +import { Pagination as MUIPagination } from '@mui/material'; +import { FC } from 'react'; + +interface Props { + count: number; + page: number; + // eslint-disable-next-line no-unused-vars + onChange: (page: number) => void; +} + +const Pagination: FC = ({ count = 12, page = 1, onChange }: Props) => { + const handlePageChange = ( + event: React.ChangeEvent, + newPage: number + ) => { + onChange(newPage); + }; + + return ( + + ); +}; + +export default Pagination; diff --git a/web/src/components/atoms/StatusChip.tsx b/web/src/components/atoms/StatusChip.tsx new file mode 100644 index 0000000..6a9a512 --- /dev/null +++ b/web/src/components/atoms/StatusChip.tsx @@ -0,0 +1,30 @@ +import { Chip } from '@mui/material'; +import { FC } from 'react'; + +interface Props { + label: string; +} + +const colors: Record = { + 'Not yet Created': '#FFB4AF', + Making: '#FDFF8F', + Approved: '#8AFFB2', + 'Sent to Customer': '#84C1FF', + Closed: '#65707b33' +}; + +const StatusChip: FC = ({ label }) => { + const color = colors[label] || '#65707b33'; + + return ( + + ); +}; + +export default StatusChip; diff --git a/web/src/components/molecules/CreatedDateRangeFilter.tsx b/web/src/components/molecules/CreatedDateRangeFilter.tsx new file mode 100644 index 0000000..81d94b3 --- /dev/null +++ b/web/src/components/molecules/CreatedDateRangeFilter.tsx @@ -0,0 +1,48 @@ +import { Box } from '@mui/material'; +import { DatePicker } from '@mui/x-date-pickers'; +import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { Moment } from 'moment'; +import { useState } from 'react'; +import styles from 'styles/Filter.module.css'; + +const CreateDateRangeFilter = () => { + const [startDate, setStartDate] = useState(); + const [endDate, setEndDate] = useState(); + + const handleStartDateChange = (date: Moment | null): void => { + setStartDate(date); + }; + + const handleEndDateChange = (date: Moment | null): void => { + setEndDate(date); + }; + + return ( + + + + {' - '} + + + + ); +}; + +export default CreateDateRangeFilter; diff --git a/web/src/components/molecules/EstimationStatusFilter.tsx b/web/src/components/molecules/EstimationStatusFilter.tsx new file mode 100644 index 0000000..f3381ce --- /dev/null +++ b/web/src/components/molecules/EstimationStatusFilter.tsx @@ -0,0 +1,50 @@ +import { + FormControl, + InputLabel, + MenuItem, + Select, + SelectChangeEvent +} from '@mui/material'; +import { useState } from 'react'; +import styles from 'styles/Filter.module.css'; + +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' } +]; + +const EstimationStatusFilter = () => { + const [status, setStatus] = useState(''); + + const handleStatusChange = (e: SelectChangeEvent) => { + setStatus(e.target.value); + }; + + return ( + + + Estimation Status + + + + ); +}; + +export default EstimationStatusFilter; diff --git a/web/src/components/molecules/NavBarItem.tsx b/web/src/components/molecules/NavBarItem.tsx index 9ad1c54..c0ad149 100644 --- a/web/src/components/molecules/NavBarItem.tsx +++ b/web/src/components/molecules/NavBarItem.tsx @@ -1,5 +1,8 @@ +'use client' + import Link from 'next/link' import React, { FC } from 'react' +import { usePathname } from 'next/navigation' import { Box, Typography } from '@mui/material' import {SvgIconComponent} from '@mui/icons-material' @@ -25,6 +28,9 @@ const NavBarItem: FC = ({ linkTo = "/" }) => { + const pathname = usePathname() + active = pathname === linkTo ? true : false + const backgroundColor = active ? 'primary.100' : 'primary.700' const textColor = active? 'dark': 'white' diff --git a/web/src/components/molecules/SearchBar.tsx b/web/src/components/molecules/SearchBar.tsx new file mode 100644 index 0000000..7811e02 --- /dev/null +++ b/web/src/components/molecules/SearchBar.tsx @@ -0,0 +1,38 @@ +import SearchIcon from '@mui/icons-material/Search'; +import { + FormControl, + InputAdornment, + InputLabel, + OutlinedInput +} from '@mui/material'; +import { useState } from 'react'; +import styles from 'styles/Filter.module.css'; + +const SearchBar = () => { + const [searchKeyword, setSearchKeyword] = useState(''); + + const handleSearch = (e: React.ChangeEvent) => { + setSearchKeyword(e.target.value); + }; + return ( + + Search Job + + + + } + /> + + ); +}; + +export default SearchBar; diff --git a/web/src/components/molecules/TagFilter.tsx b/web/src/components/molecules/TagFilter.tsx new file mode 100644 index 0000000..b6f6ac0 --- /dev/null +++ b/web/src/components/molecules/TagFilter.tsx @@ -0,0 +1,48 @@ +import { + FormControl, + InputLabel, + MenuItem, + Select, + SelectChangeEvent +} from '@mui/material'; +import { useState } from 'react'; +import styles from 'styles/Filter.module.css'; + +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' } +]; + +const TagFilter = () => { + const [tag, setTag] = useState(''); + + const handleTagChange = (e: SelectChangeEvent) => { + setTag(e.target.value); + }; + + return ( + + + Tag + + + + ); +}; + +export default TagFilter; diff --git a/web/src/components/organisms/JobListTable.tsx b/web/src/components/organisms/JobListTable.tsx new file mode 100644 index 0000000..fb807ff --- /dev/null +++ b/web/src/components/organisms/JobListTable.tsx @@ -0,0 +1,151 @@ +/* eslint-disable no-mixed-spaces-and-tabs */ +import { Column, JobData } from '@/app/page'; +import StatusChip from '@/components/atoms/StatusChip'; +import { + Box, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import { FC } from 'react'; + +interface Props { + columns: Column[]; + data: JobData[]; +} + +const JobListTable: FC = ({ columns, data }: Props) => { + return ( + + + + + + + + {columns.map((column) => ( + + + {column.label} + + + ))} + + + + {data.map((row: JobData) => ( + + {columns.map( + (column: Column, index) => { + let cellContent: JSX.Element | null = + null; + + if ( + [ + 'pipelinePhase', + 'estimationStatus' + ].includes(column.key) + ) { + const label = + column.key === + 'pipelinePhase' + ? row.pipelinePhase + : row.estimation + .status; + cellContent = ( + + ); + } else if ( + column.key === 'tags' + ) { + const tagChips = + row.tags.map( + (tag, key) => ( + + ) + ); + cellContent = ( + + {tagChips} + + ); + } else if ( + column.key === 'schedules' + ) { + const schedules = + row.schedules.map( + (schedule, key) => ( + + {schedule} + + ) + ); + cellContent = ( + + {schedules} + + ); + } else { + cellContent = ( + + {column.key === + 'cost' + ? `PHP ${row.estimation.cost}` + : (row[ + column + .key + ] as string)} + + ); + } + return ( + + {cellContent} + + ); + } + )} + + ))} + +
+
+
+
+
+ ); +}; + +export default JobListTable; diff --git a/web/src/components/organisms/SearchFilterHeader.tsx b/web/src/components/organisms/SearchFilterHeader.tsx new file mode 100644 index 0000000..9287f50 --- /dev/null +++ b/web/src/components/organisms/SearchFilterHeader.tsx @@ -0,0 +1,68 @@ +import { FilterCog } from '@/assets/icons/FilterCog'; +import { FilterRemove } from '@/assets/icons/FilterRemove'; +import TableRows from '@mui/icons-material/TableRows'; +import { Box, Button, Collapse } from '@mui/material'; +import { useState } from 'react'; +import styles from 'styles/Filter.module.css'; +import CreateDateRangeFilter from '../molecules/CreatedDateRangeFilter'; +import EstimationStatusFilter from '../molecules/EstimationStatusFilter'; +import SearchBar from '../molecules/SearchBar'; +import TagFilter from '../molecules/TagFilter'; + +const SearchFilterHeader = (): JSX.Element => { + const [isExpanded, setIsExpanded] = useState(false); + + const toggleFilters = () => { + setIsExpanded(!isExpanded); + }; + + return ( + + + + + + + + + + + + + + + ); +}; + +export default SearchFilterHeader; diff --git a/web/src/utils/constants/navbarMenu.ts b/web/src/utils/constants/navbarMenu.ts index 1d2cfc2..305e460 100644 --- a/web/src/utils/constants/navbarMenu.ts +++ b/web/src/utils/constants/navbarMenu.ts @@ -1,6 +1,6 @@ import { - Home, CalendarMonth, + Home, Person, SvgIconComponent, } from "@mui/icons-material"; @@ -20,11 +20,11 @@ export const Menus: IMenu[] = [ { name: "Calendar", Icon: CalendarMonth, - href: "/", + href: "/calendar", }, { name: "Customer", Icon: Person, - href: "/", + href: "/customer", }, ]; diff --git a/web/styles/Filter.module.css b/web/styles/Filter.module.css new file mode 100644 index 0000000..4194212 --- /dev/null +++ b/web/styles/Filter.module.css @@ -0,0 +1,36 @@ +.button { + height: 52px; + width: 52px; + background-color: white; + transition: background-color 0.3s; +} + +.button:hover { + background-color: #2A3F52; +} + +.active { + background-color: #2A3F52; +} + +.icon { + height: 20px; + width: 20px; + transition: fill 0.3s; + fill: #3E5367; +} + +.iconWhite { + height: 20px; + width: 20px; + transition: fill 0.3s; + fill: white; +} + +.button:hover .icon { + fill: white; +} + +.input { + background-color: white; +}