├── src ├── components │ ├── Budget.js │ ├── Remaining.js │ ├── AllocationForm.js │ ├── ExpenseItem.js │ ├── ExpenseList.js │ └── ExpenseTotal.js ├── .DS_Store ├── setupTests.js ├── App.test.js ├── index.css ├── reportWebVitals.js ├── index.js ├── App.css ├── App.js ├── logo.svg └── context │ └── AppContext.js ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html └── .gitignore /src/components/Budget.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/Remaining.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/AllocationForm.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/ExpenseItem.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/ExpenseList.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/ExpenseTotal.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gangludev/react-budget-app/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gangludev/react-budget-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gangludev/react-budget-app/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gangludev/react-budget-app/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | 5 | //Code to import Budget.js 6 | import Budget from './components/Budget'; 7 | 8 | // Add code to import the other components here under 9 | 10 | 11 | import { AppProvider } from './context/AppContext'; 12 | const App = () => { 13 | return ( 14 | 15 |
16 |

Company's Budget Allocation

17 |
18 |     { 19 | /* Add Budget component here */ 20 | } 21 | 22 |     { 23 | /* Add Remaining component here*/ 24 | } 25 | 26 |     { 27 | /* Add ExpenseTotal component here */ 28 | } 29 | 30 |     { 31 | /* Add ExpenseList component here */ 32 | } 33 | 34 |     { 35 | /* Add ExpenseItem component here */ 36 | } 37 | 38 |     { 39 | /* Add AllocationForm component here under */ 40 | } 41 | 42 |
43 |
44 |
45 | ); 46 | }; 47 | export default App; 48 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/context/AppContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from 'react'; 2 | 3 | // 5. The reducer - this is used to update the state, based on the action 4 | export const AppReducer = (state, action) => { 5 | let budget = 0; 6 | switch (action.type) { 7 | case 'ADD_EXPENSE': 8 | let total_budget = 0; 9 | total_budget = state.expenses.reduce( 10 | (previousExp, currentExp) => { 11 | return previousExp + currentExp.cost 12 | },0 13 | ); 14 | total_budget = total_budget + action.payload.cost; 15 | action.type = "DONE"; 16 | if(total_budget <= state.budget) { 17 | total_budget = 0; 18 | state.expenses.map((currentExp)=> { 19 | if(currentExp.name === action.payload.name) { 20 | currentExp.cost = action.payload.cost + currentExp.cost; 21 | } 22 | return currentExp 23 | }); 24 | return { 25 | ...state, 26 | }; 27 | } else { 28 | alert("Cannot increase the allocation! Out of funds"); 29 | return { 30 | ...state 31 | } 32 | } 33 | case 'RED_EXPENSE': 34 | const red_expenses = state.expenses.map((currentExp)=> { 35 | if (currentExp.name === action.payload.name && currentExp.cost - action.payload.cost >= 0) { 36 | currentExp.cost = currentExp.cost - action.payload.cost; 37 | budget = state.budget + action.payload.cost 38 | } 39 | return currentExp 40 | }) 41 | action.type = "DONE"; 42 | return { 43 | ...state, 44 | expenses: [...red_expenses], 45 | }; 46 | case 'DELETE_EXPENSE': 47 | action.type = "DONE"; 48 | state.expenses.map((currentExp)=> { 49 | if (currentExp.name === action.payload) { 50 | budget = state.budget + currentExp.cost 51 | currentExp.cost = 0; 52 | } 53 | return currentExp 54 | }) 55 | action.type = "DONE"; 56 | return { 57 | ...state, 58 | budget 59 | }; 60 | case 'SET_BUDGET': 61 | action.type = "DONE"; 62 | state.budget = action.payload; 63 | 64 | return { 65 | ...state, 66 | }; 67 | case 'CHG_CURRENCY': 68 | action.type = "DONE"; 69 | state.currency = action.payload; 70 | return { 71 | ...state 72 | } 73 | 74 | default: 75 | return state; 76 | } 77 | }; 78 | 79 | // 1. Sets the initial state when the app loads 80 | const initialState = { 81 | budget: 2000, 82 | expenses: [ 83 | { id: "Marketing", name: 'Marketing', cost: 50 }, 84 | { id: "Finance", name: 'Finance', cost: 300 }, 85 | { id: "Sales", name: 'Sales', cost: 70 }, 86 | { id: "Human Resource", name: 'Human Resource', cost: 40 }, 87 | { id: "IT", name: 'IT', cost: 500 }, 88 | ], 89 | currency: '£' 90 | }; 91 | 92 | // 2. Creates the context this is the thing our components import and use to get the state 93 | export const AppContext = createContext(); 94 | 95 | // 3. Provider component - wraps the components we want to give access to the state 96 | // Accepts the children, which are the nested(wrapped) components 97 | export const AppProvider = (props) => { 98 | // 4. Sets up the app state. takes a reducer, and an initial state 99 | const [state, dispatch] = useReducer(AppReducer, initialState); 100 | let remaining = 0; 101 | 102 | if (state.expenses) { 103 | const totalExpenses = state.expenses.reduce((total, item) => { 104 | return (total = total + item.cost); 105 | }, 0); 106 | remaining = state.budget - totalExpenses; 107 | } 108 | 109 | return ( 110 | 119 | {props.children} 120 | 121 | ); 122 | }; 123 | --------------------------------------------------------------------------------