Add files via upload

This commit is contained in:
alhan
2022-07-05 12:24:54 +03:00
committed by GitHub
commit 382a413676
36 changed files with 31766 additions and 0 deletions

0
README.md Normal file
View File

12
build/asset-manifest.json Normal file
View File

@@ -0,0 +1,12 @@
{
"files": {
"main.css": "/static/css/main.9f0329a3.css",
"main.js": "/static/js/main.3f5e9df8.js",
"static/js/27.472c58b9.chunk.js": "/static/js/27.472c58b9.chunk.js",
"index.html": "/index.html"
},
"entrypoints": [
"static/css/main.9f0329a3.css",
"static/js/main.3f5e9df8.js"
]
}

BIN
build/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

1
build/index.html Normal file
View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.3f5e9df8.js"></script><link href="/static/css/main.9f0329a3.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

BIN
build/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
build/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
build/manifest.json Normal file
View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
build/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1 @@
.box{background-color:aqua}.textField{margin:0!important}.jobListFilter{background-color:#daf0ff;padding-bottom:8px;padding-right:8px}.jobListFilterField{background-color:#fff}.jobListTitles{background-color:#bfe3fb;margin-top:0!important;padding-bottom:4px}.jobListLine{margin-top:8px}.jobListLine>div{background-color:#eee;border:1px solid #ccc;border-top:none}.jobListLine:nth-child(2n)>div{background-color:#fff}.priorityBox{border-radius:5px;color:#fff;font-size:14px;margin:0 auto 7px;max-width:100px;min-width:60px;padding:2px 0}.priority-Urgent{background-color:#ff4040}.priority-Regular{background-color:#ffb937}.priority-Trivial{background-color:#2f86ff}.footer{background-color:#dcebff;margin-left:-8px;padding:5px;text-align:right}.footer>span{font-size:12px;padding-right:5px}

View File

@@ -0,0 +1 @@
"use strict";(self.webpackChunkrisetect_assessment=self.webpackChunkrisetect_assessment||[]).push([[27],{27:function(e,t,n){n.r(t),n.d(t,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},s=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},f=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){s((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),f((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(s&&s.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],s=o?null:c("paint",a);(o||s)&&(n=m(e,r,t),o&&a(o),f((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),s((function(){p.takeRecords().map(v),n(!0)})),f((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&s((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&f((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},k={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),s(v,!0),f((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,133 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license MUI v5.8.0
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

10
firebase.json Normal file
View File

@@ -0,0 +1,10 @@
{
"hosting": {
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}

30831
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "risetect-assessment",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@fontsource/roboto": "^4.5.7",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.8.6",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"firebase": "^9.8.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.33.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "set \"GENERATE_SOURCEMAP=false\" && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json Normal file
View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

40
src/App.js Normal file
View File

@@ -0,0 +1,40 @@
import { LensBlur } from '@mui/icons-material';
import ContextProvider from './utils/ContextProvider';
import JobList from './comps/JobList';
import CreateJob from './comps/CreateJob';
import { Container, Typography, Box, Grid, Divider } from '@mui/material';
import './mui/custom.css';
function App() {
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Grid container spacing={1}>
<Grid item>
<LensBlur fontSize="large" color="primary" />
</Grid>
<Grid item>
<Typography variant="h4" component="h2" gutterBottom color="primary">
Logo
</Typography>
</Grid>
</Grid>
<Divider sx={{ my: 2 }} />
<ContextProvider>
<CreateJob />
<Divider sx={{ my: 2 }} />
<JobList />
</ContextProvider>
<Box className='footer'>
<Typography variant="p" gutterBottom>
Alhan Özdemir @2022
</Typography>
</Box>
</Box>
</Container>
);
}
export default App;

8
src/App.test.js Normal file
View File

@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

100
src/comps/CreateJob.js Normal file
View File

@@ -0,0 +1,100 @@
import React, { Fragment } from "react";
import { Context } from '../utils/ContextProvider';
import { Button, Typography, TextField, Grid, FormControl, InputLabel, Select, MenuItem } from '@mui/material';
import { useForm, Controller } from 'react-hook-form';
export default function CreateJob(props) {
const { register, handleSubmit, reset, getValues, setValue, formState, control } = useForm({
mode: 'onChange',
defaultValues: {
name: '',
priority: '',
},
validationFields: ['priority']
});
const values = getValues();
return (
<Context.Consumer>
{context => (
<Fragment>
<Typography variant="h5" gutterBottom color="black">Create New Job</Typography>
<form onSubmit={handleSubmit((data) => {
context.saveData(data);
reset({
name: '',
priority: '',
});
})}>
<Grid container spacing={1}>
<Grid item md={8} xs={12}>
<Controller
name="name"
control={control}
defaultValue=""
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
label="Job Name"
size="small"
value={value}
onChange={onChange}
error={!!error}
helperText={error ? error.message : null}
{...register('name', {
pattern: {
value: /^[A-Za-z0-9 ıİüÜğĞçÇöÖşŞ]{1,255}$/,
message: 'Only Alphanumeric chars' // JS only: <p>error message</p> TS only support string
}
})}
{...register('name', { maxLength: { value: 255, message: "max 255 chars" } })}
/>
)}
rules={{ required: 'Job name required' }}
/>
</Grid>
<Grid item md={2} xs={6}>
<Controller
control={control}
name="priority"
render={({ field: { onChange, onBlur, value, ref }, formState }) => (
<FormControl fullWidth size="small">
<InputLabel id="demo-select-small">Priority</InputLabel>
<Select
fullWidth
labelId="demo-select-small"
id="demo-select-small"
label="Priority"
value={value}
defaultValue={values.priority}
onBlur={onBlur} // notify when input is touched
onChange={e => setValue('priority', e.target.value, true)}
{...register("priority", {
required: "Required",
})}
>
<MenuItem value="">SELECT ONE</MenuItem>
{Object.keys(context.priority).map(pid => (
<MenuItem key={pid} value={pid}>{context.priority[pid]}</MenuItem>
))}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={2} xs={6}>
<Button type="submit" variant="contained" fullWidth disabled={!formState.isValid}>CREATE</Button>
</Grid>
</Grid>
</form>
</Fragment>
)}
</Context.Consumer>
)
};

62
src/comps/Job.js Normal file
View File

@@ -0,0 +1,62 @@
import { useState } from 'react';
import { Edit, Delete } from '@mui/icons-material';
import { Grid, IconButton, Box } from '@mui/material';
import DeleteDialog from '../mui/deleteDialog';
import EditDialog from '../mui/editDialog';
import '../mui/custom.css';
export default function Job(props) {
const [open, setOpen] = useState(false);
const [openEdit, setOpenEdit] = useState(false);
const handleClose = (value) => {
setOpen(false);
if (value !== false) {
props.deleteClick(value);
}
};
const handleCloseEdit = (value) => {
setOpenEdit(false);
if (value !== false) {
props.editClick(value);
}
};
return (
<Box className="jobListLine">
<DeleteDialog
open={open}
onClose={handleClose}
jobName={props.name}
jobId={props.jobId}
/>
<EditDialog
open={openEdit}
onClose={handleCloseEdit}
jobName={props.name}
jobPriority={props.priority}
jobId={props.jobId}
/>
<Grid container spacing={1}>
<Grid item md={8} sm={8} xs={6}>{props.name}</Grid>
<Grid item md={2} sm={2} xs={3} textAlign="center">
<Box className={"priorityBox priority-" + props.priority}>
{props.priority}
</Box>
</Grid>
<Grid item md={2} sm={2} xs={3} textAlign="right" paddingRight={1}>
<IconButton aria-label="edit" size="small"
onClick={(e) => setOpenEdit(true)} >
<Edit fontSize="inherit" />
</IconButton>
<IconButton aria-label="delete" size="small"
onClick={(e) => setOpen(true)} >
<Delete fontSize="inherit" />
</IconButton>
</Grid>
</Grid>
</Box>
);
}

89
src/comps/JobList.js Normal file
View File

@@ -0,0 +1,89 @@
import { Context } from '../utils/ContextProvider';
import Job from "./Job";
import { Typography, Box, Grid, TextField, Select, MenuItem, FormControl, InputLabel } from '@mui/material';
import { useContext, useState } from 'react';
import '../mui/custom.css';
export default function JobList() {
const [NameState, setNameState] = useState("");
const [PriorityState, setPriorityState] = useState("");
const context = useContext(Context);
const jobsAll = context.jobs;
const jobsSort = Object.values(jobsAll).sort(function (a, b) {
if (a.priority < b.priority) {
return -1;
} else if (a.priority === b.priority) {
if (a.name < b.name) return -1;
else return 1;
} else return 1;
});
const jobsFiltered = jobsSort.filter(item => PriorityState !== "" ?
item.priority.includes(PriorityState) : true)
.filter(item => NameState !== "" ? item.name.includes(NameState) : true);
return (
<Box sx={{ my: 2 }} >
<Typography variant="h5" gutterBottom color="black">Job List</Typography>
<Grid container spacing={1} className="jobListFilter">
<Grid item md={8} xs={9}>
<TextField
className='jobListFilterField'
fullWidth
id="outlined-name"
label="Name"
size="small"
value={NameState}
onChange={(e) => setNameState(e.target.value)}
/>
</Grid>
<Grid item md={4} xs={3} textAlign="center">
<FormControl fullWidth size="small">
<InputLabel id="demo-select-small">Priority</InputLabel>
<Select
className='jobListFilterField'
fullWidth
labelId="demo-select-small"
id="demo-select-small"
label="Priority"
value={PriorityState}
onChange={(e) => setPriorityState(e.target.value)}
>
<MenuItem value="">All</MenuItem>
{Object.keys(context.priority).map(pid => (
<MenuItem
key={pid}
value={pid}>
{context.priority[pid]}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
<Grid container spacing={1} className='jobListTitles'>
<Grid item sm={8} xs={6}>
<Box>Name</Box>
</Grid>
<Grid item sm={2} xs={3} textAlign="center">Priority</Grid>
<Grid item sm={2} xs={3} textAlign="right" paddingRight={2}>Action</Grid>
</Grid>
{jobsFiltered.map(jobID => (
<Job
key={jobID.id}
jobId={jobID.id}
name={jobID.name}
priority={context.priority[jobID.priority]}
editClick={(e) => context.editClick(e)}
deleteClick={(e) => context.deleteClick(jobID.id)}
/>
))}
</Box>
);
}

22
src/index.js Normal file
View File

@@ -0,0 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import theme from './mui/theme';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

55
src/mui/custom.css Normal file
View File

@@ -0,0 +1,55 @@
.box{
background-color: aqua;
}
.textField{
margin: 0 !important;
}
.jobListFilter{
background-color:#daf0ff;
padding-bottom: 8px;
padding-right: 8px;
}
.jobListFilterField{
background-color:#ffffff;
}
.jobListTitles{
background-color: #bfe3fb;
margin-top: 0px!important;
padding-bottom: 4px;
}
.jobListLine{
margin-top:8px;
}
.jobListLine > div{
background-color:#eee;
border: 1px solid #ccc;
border-top: none;
}
.jobListLine:nth-child(2n) > div{
background-color:#ffffff;
}
.priorityBox{
max-width: 100px;
min-width: 60px;
margin:0 auto 7px;
border-radius: 5px;
color: #fff;
font-size: 14px;
padding: 2px 0;
}
.priority-Urgent{
background-color: rgb(255, 64, 64);
}
.priority-Regular{
background-color: rgb(255, 185, 55);
}
.priority-Trivial{
background-color: rgb(47, 134, 255);
}
.footer{
background-color: rgb(220 235 255);
text-align: right;
margin-left: -8px;
padding: 5px;
}
.footer > span{ font-size: 12px; padding-right: 5px;}

37
src/mui/deleteDialog.js Normal file
View File

@@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import { Report } from '@mui/icons-material';
import { Dialog, Button, Stack, Typography } from '@mui/material';
export default function DeleteDialog(props) {
const { onClose, open, jobName, jobId } = props;
const handleClose = () => {
onClose(false);
};
const handleApprove = (value) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open} maxWidth={"xs"} fullWidth>
<Typography variant='h4' m={0} mt={2} textAlign="center">Delete Job</Typography>
<Typography variant='p' m={1} textAlign="center">{jobName}</Typography>
<Stack alignItems="center">
<Report fontSize='large' color='error' />
</Stack>
<Stack direction="row" spacing={5} alignSelf={"center"} padding={3}>
<Button variant="contained" fullWidth
onClick={() => handleClose()}>Cancel</Button>
<Button variant="contained" color='error' fullWidth
onClick={() => handleApprove(jobId)}>Approve</Button>
</Stack>
</Dialog>
);
}
DeleteDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
jobName: PropTypes.string.isRequired,
jobId: PropTypes.string.isRequired,
};

109
src/mui/editDialog.js Normal file
View File

@@ -0,0 +1,109 @@
import PropTypes from 'prop-types';
import { Context } from '../utils/ContextProvider';
import { Dialog, DialogTitle, Button, Stack, TextField, InputLabel, FormControl, Select, MenuItem } from '@mui/material';
import { useForm, Controller } from 'react-hook-form';
export default function EditDialog(props) {
const { onClose, open, jobName, jobPriority, jobId } = props;
const { register, handleSubmit, getValues, setValue, formState, control } = useForm({
mode: 'onChange',
defaultValues: {
name: jobName,
priority: jobPriority,
},
validationFields: ['priority']
});
const values = getValues();
const handleClose = () => {
onClose(false);
};
const handleSave = (value) => {
onClose(value);
};
return (
<Dialog onClose={handleClose} open={open} maxWidth={"xs"} fullWidth>
<DialogTitle textAlign={"center"}>Edit Job</DialogTitle>
<form onSubmit={handleSubmit((data) => {
handleSave({ id: jobId, data: data });
})}>
<Stack direction="column" spacing={3} alignSelf={"center"} paddingX={3} >
<Controller
name="name"
padding={3}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
fullWidth
label="Job Name"
size="small"
disabled
value={formState.name}
onChange={onChange}
error={!!error}
helperText={error ? error.message : null}
{...register('name', {
pattern: {
value: /^[A-Za-z0-9 ıİüÜğĞçÇöÖşŞ]{1,255}$/,
message: 'Only Alphanumeric chars'
}
})}
{...register('name', { maxLength: { value: 255, message: "max 255 chars" } })}
/>
)}
rules={{ required: 'Job name required' }}
/>
<Context.Consumer>
{context => (
<Controller
control={control}
name="priority"
render={({ field: { onChange, onBlur, value, ref }, formState }) => (
<FormControl fullWidth size="small">
<InputLabel id="demo-select-small">Priority</InputLabel>
<Select
fullWidth
labelId="demo-select-small"
id="demo-select-small"
label="Priority"
value={value}
defaultValue={values.priority}
onBlur={onBlur}
onChange={e => setValue('priority', e.target.value, true)}
{...register("priority", {
required: "Required",
})}
>
{Object.keys(context.priority).map(pid => (
<MenuItem key={pid} value={context.priority[pid]}>{context.priority[pid]}</MenuItem>
))}
</Select>
</FormControl>
)}
/>
)}
</Context.Consumer>
</Stack>
<Stack direction="row" spacing={4} justifyContent="center" padding={4} >
<Button variant="contained" size='small' fullWidth
onClick={() => handleClose()}>Cancel</Button>
<Button type="submit" size="small" variant="contained" color='error' fullWidth>Save</Button>
</Stack>
</form>
</Dialog>
);
}
EditDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
jobName: PropTypes.string.isRequired,
jobPriority: PropTypes.string.isRequired,
jobId: PropTypes.string.isRequired,
};

18
src/mui/theme.js Normal file
View File

@@ -0,0 +1,18 @@
import { red } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
});
export default theme;

13
src/reportWebVitals.js Normal file
View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { ref, set, onValue } from "firebase/database";
import { db } from "./firebase";
export const Context = React.createContext();
export default function ContextProvider(props) {
const [JobsState, setJobsState] = useState(null);
const priority = ["Urgent", "Regular", "Trivial"];
const initDB = JobsState === null ? getDB() : true;
if (!initDB) return (<div className='loading'>loading</div>);
function getDB() {
onValue(ref(db, "jobs/"), (snapshot) => {
if (snapshot.val())
setJobsState(snapshot.val());
else
setJobsState("");
});
return false;
}
return (
<Context.Provider
value={{
jobs: JobsState,
priority: priority,
saveData: (data) => {
const rnd = Date.now() + "-" + Math.floor(Math.random() * 1000000000).toString();
const record = {
id: rnd,
name: data.name,
priority: data.priority
}
set(ref(db, "jobs/" + rnd), record);
},
editClick: (v) => {
//console.log(v);
set(ref(db, "jobs/" + v.id), v.data);
},
deleteClick: (jobId) => {
//console.log(jobId);
set(ref(db, "jobs/" + jobId), null);
}
}}
>
{props.children}
</Context.Provider>
);
}

16
src/utils/firebase.js Normal file
View File

@@ -0,0 +1,16 @@
import { initializeApp } from 'firebase/app';
import { getDatabase } from "firebase/database";
const firebaseConfig = {
apiKey: "xxx",
authDomain: "risetech-assessment.firebaseapp.com",
databaseURL: "https://risetech-assessment-default-rtdb.europe-west1.firebasedatabase.app",
projectId: "risetech-assessment",
storageBucket: "risetech-assessment.appspot.com",
messagingSenderId: "xxx",
appId: "xxx"
};
const app = initializeApp(firebaseConfig);
export const db = getDatabase(app);

5
src/utils/general.js Normal file
View File

@@ -0,0 +1,5 @@
export const reverseArray = arr => {
let newArr = [];
for (let i = arr.length - 1; i >= 0; i--) newArr.push(arr[i]);
return newArr;
}