Skip to content

Commit

Permalink
[M1_TR-213] Frontend for M1_TR-211
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoleamber committed Sep 14, 2023
1 parent 1650201 commit 926dafd
Show file tree
Hide file tree
Showing 19 changed files with 404 additions and 248 deletions.
6 changes: 5 additions & 1 deletion server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
cors: {
origin: process.env.FRONTEND_URL
}
});

const config = new DocumentBuilder()
.setTitle('Sim-JMS')
Expand Down
3 changes: 3 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.6",
"@mui/material": "^5.14.3",
"@mui/x-date-pickers": "^6.13.0",
"@types/node": "20.4.8",
"@types/react": "18.2.18",
"@types/react-dom": "18.2.7",
"axios": "^1.5.0",
"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",
Expand Down
91 changes: 88 additions & 3 deletions web/src/app/job/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { JobTable } from '@/utils/types/job';
import { useContext, useState } from 'react';
import { axiosInstance } from '@/utils/services/axios';
import { JobSchema, JobTable, JobTableRow } from '@/utils/types/job';
import { ScheduleSchema } from '@/utils/types/schedule';
import { useContext, useEffect, useState } from 'react';
import { JobListContext } from './context';

export const useJobListContext = (): JobTable => {
Expand All @@ -14,9 +16,92 @@ export const useJobListContext = (): JobTable => {

export const useHooks = () => {
const [page, setPage] = useState(1);
const [perPage] = useState(12);
const [jobs, setJobs] = useState<JobTableRow[]>([]);
const [count, setCount] = useState(0);
const [pageCount, setPageCount] = useState(1);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | undefined>();

useEffect(() => {
axiosInstance
.get('/jobs', {
params: {
page,
perPage
}
})
.then((response) => {
setCount(response?.data.count);
setJobs(convertTableData(response?.data.jobs));
setPageCount(Math.ceil(response?.data.count / perPage));
setIsLoading(false);
})
.catch((e) => {
console.log(e);
setIsLoading(false);
setError('Something went wrong.');
});
}, [page, perPage]);

return {
jobs,
count,
page,
setPage
pageCount,
isLoading,
error,
setPage,
};
};

const formatDate = (date: string): string => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
};

const formatTime = (date: string): string => {
return new Date(date).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
};

const formatSchedules = (schedules: ScheduleSchema[]): string[] => {
const scheduleData = schedules.map((schedule: ScheduleSchema) => {
const startDateTime = `${formatDate(schedule.startDate)} ${formatTime(schedule.startTime)}`;
const endDateTime = `${formatTime(schedule.endTime)}`;
return `${startDateTime} - ${endDateTime}`;
});

return scheduleData;
};

const formatEnum = (value: string): string => {
let words = value.split('_');
words = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
return words.join(' ');
}

const convertTableData = (data: JobSchema[]): JobTableRow[] => {
const tableData = data.map((job: JobSchema) => ({
id: job.id,
title: job.title,
customer: `${job.customer.firstName} ${job.customer.lastName}`,
tags: job.tags.map((tag) => formatEnum(tag)).sort(),
schedules: formatSchedules(job.schedules).sort(),
estimation: {
status: job.estimation?.status ? formatEnum(job.estimation?.status) : 'Not Yet Created',
cost: job.estimation?.totalCost ? `₱ ${job.estimation?.totalCost}` : '-'
},
personInCharge: `${job.personInCharge.firstName} ${job.personInCharge.lastName}`,
pipelinePhase: formatEnum(job.pipelinePhase),
createdAt: formatDate(job.createdAt)
}));

return tableData;
};
70 changes: 50 additions & 20 deletions web/src/app/job/page.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,67 @@
'use client';

import Pagination from '@/components/atoms/Pagination';
import StatusDisplay from '@/components/molecules/StatusDisplay';
import JobListTable from '@/components/organisms/JobListTable';
import SearchFilterHeader from '@/components/organisms/SearchFilterHeader';
import { JobColumns, JobData } from '@/utils/constants/jobTableData';
import { Grid } from '@mui/material';
import { JobColumns } from '@/utils/constants/jobTableData';
import { Grid, Typography } from '@mui/material';
import { Fragment } from 'react';
import { JobListContext } from './context';
import { useHooks } from './hooks';

const JobList = (): JSX.Element => {
const { page, setPage } = useHooks();
const { page, jobs, count, pageCount, setPage, isLoading, error } =
useHooks();

return (
<main>
<JobListContext.Provider
value={{ columns: JobColumns, data: JobData }}>
<Grid
container
sx={{
padding: 3,
gap: 3,
flexDirection: 'column'
}}>
<Grid item>
<SearchFilterHeader />
value={{ columns: JobColumns, data: jobs }}>
{isLoading ? (
<StatusDisplay isLoading={isLoading} />
) : error ? (
<StatusDisplay error={error} />
) : (
<Grid
container
sx={{
padding: 3,
gap: 3,
flexDirection: 'column'
}}>
<Grid item>
<SearchFilterHeader />
</Grid>
{!count ? (
<Grid
item
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '60vh'
}}>
<Typography variant='label1r'>
No jobs found
</Typography>
</Grid>
) : (
<Fragment>
<Grid item>
<JobListTable />
</Grid>
<Grid item sx={{ alignSelf: 'center' }}>
<Pagination
count={pageCount}
page={page}
onChange={setPage}
/>
</Grid>
</Fragment>
)}
</Grid>
<Grid item>
<JobListTable />
</Grid>
<Grid item sx={{ alignSelf: 'center' }}>
<Pagination count={12} page={page} onChange={setPage} />
</Grid>
</Grid>
)}
</JobListContext.Provider>
</main>
);
Expand Down
29 changes: 14 additions & 15 deletions web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import type { Metadata } from 'next'
import { ThemeProvider } from '@mui/material'
import { ThemeProvider } from '@mui/material';
import type { Metadata } from 'next';

import { theme } from '@/assets/theme'
import { theme } from '@/assets/theme';
import Wrapper from './wrapper';

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
title: 'JMS'
};

export default function RootLayout({
children,
children
}: {
children: React.ReactNode
children: React.ReactNode;
}) {
return (
<html lang="en">
<ThemeProvider theme={theme}>
<Wrapper>{children}</Wrapper>
</ThemeProvider>
</html>
)
return (
<html lang='en'>
<ThemeProvider theme={theme}>
<Wrapper>{children}</Wrapper>
</ThemeProvider>
</html>
);
}
18 changes: 13 additions & 5 deletions web/src/components/molecules/CreatedDateRangeFilter/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Moment } from 'moment';
import { useState } from 'react';
import moment, { type Moment } from 'moment';
import { useEffect, useState } from 'react';

export const useHooks = () => {
const [startDate, setStartDate] = useState<Moment | null>();
const [endDate, setEndDate] = useState<Moment | null>();
const [startDate, setStartDate] = useState<Moment | null>(moment().startOf('month'));
const [endDate, setEndDate] = useState<Moment | null>(moment());
const [isInvalidDate, setIsInvalidDate] = useState(false);

const handleStartDateChange = (date: Moment | null): void => {
setStartDate(date);
Expand All @@ -13,10 +14,17 @@ export const useHooks = () => {
setEndDate(date);
};

useEffect(() => {
if (startDate && endDate) {
setIsInvalidDate(endDate <= startDate);
}
}, [startDate, endDate]);

return {
startDate,
handleStartDateChange,
endDate,
handleEndDateChange
handleEndDateChange,
isInvalidDate
};
};
42 changes: 35 additions & 7 deletions web/src/components/molecules/CreatedDateRangeFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,73 @@
'use-client';

import styles from '@/styles/Filter.module.css';
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 { useHooks } from './hooks';

const CreateDateRangeFilter = () => {
const { startDate, handleStartDateChange, endDate, handleEndDateChange } =
useHooks();
const {
startDate,
handleStartDateChange,
endDate,
handleEndDateChange,
isInvalidDate
} = useHooks();

return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<LocalizationProvider dateAdapter={AdapterMoment}>
<DatePicker
disableFuture
label='Created at - Start Date'
value={startDate}
onChange={handleStartDateChange}
className={styles.input}
sx={{
'& .MuiInputBase-root': {
backgroundColor: 'white'
},
'& .MuiFormHelperText-root .Mui-error': {
backgroundColor: 'transparent'
}
}}
slotProps={{
textField: {
size: 'small',
color: 'secondary',
id: 'created-start',
name: 'created-start'
name: 'created-start',
error: isInvalidDate,
helperText: isInvalidDate
? 'Please set a valid date'
: ''
}
}}
/>
{' - '}
<DatePicker
disableFuture
label='Created at - End Date'
value={endDate}
onChange={handleEndDateChange}
className={styles.input}
sx={{
'& .MuiInputBase-root': {
backgroundColor: 'white'
},
'& .MuiFormHelperText-root .Mui-error': {
backgroundColor: 'transparent'
}
}}
slotProps={{
textField: {
size: 'small',
color: 'secondary',
id: 'created-end',
name: 'created-end'
name: 'created-end',
error: isInvalidDate,
helperText: isInvalidDate
? 'Please set a valid date.'
: ''
}
}}
/>
Expand Down
Loading

0 comments on commit 926dafd

Please sign in to comment.