mirror of
https://github.com/alhan/joblist.git
synced 2026-08-23 04:50:19 +00:00
Add files via upload
This commit is contained in:
12
build/asset-manifest.json
Normal file
12
build/asset-manifest.json
Normal 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
BIN
build/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
1
build/index.html
Normal file
1
build/index.html
Normal 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
BIN
build/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
build/logo512.png
Normal file
BIN
build/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
build/manifest.json
Normal file
25
build/manifest.json
Normal 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
3
build/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
1
build/static/css/main.9f0329a3.css
Normal file
1
build/static/css/main.9f0329a3.css
Normal 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}
|
||||
1
build/static/js/27.472c58b9.chunk.js
Normal file
1
build/static/js/27.472c58b9.chunk.js
Normal 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)}))}}}]);
|
||||
2
build/static/js/main.3f5e9df8.js
Normal file
2
build/static/js/main.3f5e9df8.js
Normal file
File diff suppressed because one or more lines are too long
133
build/static/js/main.3f5e9df8.js.LICENSE.txt
Normal file
133
build/static/js/main.3f5e9df8.js.LICENSE.txt
Normal 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
10
firebase.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"hosting": {
|
||||
"public": "build",
|
||||
"ignore": [
|
||||
"firebase.json",
|
||||
"**/.*",
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
30831
package-lock.json
generated
Normal file
30831
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
package.json
Normal file
45
package.json
Normal 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
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
43
public/index.html
Normal file
43
public/index.html
Normal 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
BIN
public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/logo512.png
Normal file
BIN
public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
25
public/manifest.json
Normal file
25
public/manifest.json
Normal 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
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
40
src/App.js
Normal file
40
src/App.js
Normal 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
8
src/App.test.js
Normal 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
100
src/comps/CreateJob.js
Normal 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
62
src/comps/Job.js
Normal 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
89
src/comps/JobList.js
Normal 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
22
src/index.js
Normal 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
55
src/mui/custom.css
Normal 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
37
src/mui/deleteDialog.js
Normal 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
109
src/mui/editDialog.js
Normal 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
18
src/mui/theme.js
Normal 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
13
src/reportWebVitals.js
Normal 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
5
src/setupTests.js
Normal 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';
|
||||
52
src/utils/ContextProvider.js
Normal file
52
src/utils/ContextProvider.js
Normal 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
16
src/utils/firebase.js
Normal 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
5
src/utils/general.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user