diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index a11777c..0000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/index.html b/public/index.html
index aa069f2..cb019b0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,42 +2,17 @@
-
+
+
+
);
}
+
export default App;
diff --git a/src/App.test.js b/src/App.test.js
index 1f03afe..15a55d4 100644
--- a/src/App.test.js
+++ b/src/App.test.js
@@ -1,8 +1,8 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
+// import { render, screen } from '@testing-library/react';
+// import App from './App';
-test('renders learn react link', () => {
- render(
);
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
+// test('renders learn react link', () => {
+// render(
);
+// const linkElement = screen.getByText(/learn react/i);
+// expect(linkElement).toBeInTheDocument();
+// });
diff --git a/src/components/Costs/CostDate.css b/src/components/Costs/CostDate.css
new file mode 100644
index 0000000..e8c739d
--- /dev/null
+++ b/src/components/Costs/CostDate.css
@@ -0,0 +1,26 @@
+.cost-date{
+ display: flex;
+ flex-direction: column;
+ width: 5.5rem;
+ height: 5.5rem;
+ border: 1px solid rgb(222, 237, 248);
+ background-color: rgb(16, 39, 243);
+ color: white;
+ border-radius: 12px;
+ align-items: center;
+ justify-content: center
+}
+
+.cost-date__month {
+ font-size: 0.75rem;
+ font-weight: bold;
+}
+
+.cost-date__year {
+ font-size: 0.75rem;
+}
+
+.cost-date__day{
+ font-size: 0.75rem;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/components/Costs/CostDate.js b/src/components/Costs/CostDate.js
new file mode 100644
index 0000000..bdfc84d
--- /dev/null
+++ b/src/components/Costs/CostDate.js
@@ -0,0 +1,17 @@
+import './CostDate.css';
+
+const CostDate = (props) => {
+ const month = props.date.toLocaleString("ua-UA", { month: "long" });
+ const year = props.date.getFullYear();
+ const day = props.date.toLocaleString("ua-UA", { day: "2-digit" });
+
+ return (
+
+
{month}
+
{year}
+
{day}
+
+ );
+}
+
+export default CostDate;
diff --git a/src/components/Costs/CostItem.css b/src/components/Costs/CostItem.css
new file mode 100644
index 0000000..ec7952a
--- /dev/null
+++ b/src/components/Costs/CostItem.css
@@ -0,0 +1,67 @@
+.cost-item {
+ max-width: 700px;
+ margin: 0 auto;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem;
+ margin: 1rem auto;
+ background-color: #ec8383;
+
+}
+
+.cost-item__date {
+ width: 30%;
+ font-size: 1rem;
+ color: rgb(173, 234, 157);
+ text-align: left;
+}
+
+.cost-item__description {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ align-items: flex-end;
+ flex-flow: column-reverse;
+ justify-content: flex-start;
+ flex: 1;
+}
+
+.cost-item h2 {
+ color: aqua;
+ font-size: 1rem;
+ flex: 1;
+ margin: 0 1rem;
+ color: white;
+ text-align: center;
+
+}
+
+.cost-item__price {
+ font-size: 1rem;
+ font-weight: bold;
+ color: #c6ddf6;
+ background-color: rgb(16, 39, 243);
+ border: 1px solid rgb(222, 237, 248);
+ padding: 0.5rem;
+ border-radius: 10px;
+
+}
+
+@media (min-width: 580px) {
+ .cost-item__description {
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ flex: 1;
+ }
+
+ .cost-item__description h2 {
+ font-size: 1.25rem;
+ }
+
+ .cost-item__price {
+ font-size: 1.25rem;
+ padding: 0.5rem 1.5rem;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Costs/CostItem.js b/src/components/Costs/CostItem.js
new file mode 100644
index 0000000..bf1d7bd
--- /dev/null
+++ b/src/components/Costs/CostItem.js
@@ -0,0 +1,19 @@
+import "./CostItem.css";
+import CostDate from "./CostDate";
+import Card from "../UI/Card";
+
+const CostItem = (props) => {
+ return (
+
+
+
+
+
{props.description}
+
${props.amount}
+
+
+
+ );
+}
+
+export default CostItem;
diff --git a/src/components/Costs/CostList.css b/src/components/Costs/CostList.css
new file mode 100644
index 0000000..d5155f7
--- /dev/null
+++ b/src/components/Costs/CostList.css
@@ -0,0 +1,9 @@
+.cost-list {
+ list-style: none;
+ padding: 0;
+}
+
+.cost-list__fallback {
+ color: #f1552d;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/src/components/Costs/CostList.js b/src/components/Costs/CostList.js
new file mode 100644
index 0000000..cd4dec0
--- /dev/null
+++ b/src/components/Costs/CostList.js
@@ -0,0 +1,22 @@
+import CostItem from "./CostItem";
+import './CostList.css';
+
+const CostList = (props) => {
+
+ if (props.costs.length === 0) {
+ return
There are no expenses in this year.
+ }
+
+ return
+ {props.costs.map((cost) => (
+
+ ))};
+
+}
+
+export default CostList;
\ No newline at end of file
diff --git a/src/components/Costs/Costs.css b/src/components/Costs/Costs.css
new file mode 100644
index 0000000..56c3aa0
--- /dev/null
+++ b/src/components/Costs/Costs.css
@@ -0,0 +1,9 @@
+.costs {
+ padding: 1rem;
+ background-color: rgb(143, 218, 245);
+ margin: 2rem auto;
+ width: 70rem;
+ max-width: 90%;
+ border-radius: 10px;
+ box-shadow: 0 1px 7px reba(0 0 0 0.25);
+}
\ No newline at end of file
diff --git a/src/components/Costs/Costs.js b/src/components/Costs/Costs.js
new file mode 100644
index 0000000..4f3c0d2
--- /dev/null
+++ b/src/components/Costs/Costs.js
@@ -0,0 +1,32 @@
+import "./Costs.css";
+import Card from "../UI/Card";
+import CostsFilter from "./CostsFilter";
+import React, { useState } from "react";
+import CostList from "./CostList";
+import CostsDiagram from "./CostsDiagram";
+
+const Costs = (props) => {
+ const [selectedYear, setSelectedYear] = useState("2020");
+
+ const yearChangeHandler = (year) => {
+ setSelectedYear(year);
+ };
+
+ const filteredCosts = props.costs.filter((cost) => {
+ return cost.date.getFullYear().toString() === selectedYear;
+ });
+
+
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default Costs;
diff --git a/src/components/Costs/CostsDiagram.js b/src/components/Costs/CostsDiagram.js
new file mode 100644
index 0000000..639e697
--- /dev/null
+++ b/src/components/Costs/CostsDiagram.js
@@ -0,0 +1,28 @@
+import Diagram from "../Diagram/Diagram"
+
+const CostsDiagram = (props) => {
+
+ const diagramDataSets = [
+ { label: 'Jan', value: 0 },
+ { label: 'Feb', value: 0 },
+ { label: 'Mar', value: 0 },
+ { label: 'Apr', value: 0 },
+ { label: 'May', value: 0 },
+ { label: 'Jun', value: 0 },
+ { label: 'Jul', value: 0 },
+ { label: 'Aug', value: 0 },
+ { label: 'Sep', value: 0 },
+ { label: 'Oct', value: 0 },
+ { label: 'Nov', value: 0 },
+ { label: 'Dec', value: 0 },
+ ];
+
+ for (const cost of props.costs) {
+ const costMonth = cost.date.getMonth();
+ diagramDataSets[costMonth].value += cost.amount;
+ }
+
+ return
+}
+
+export default CostsDiagram;
\ No newline at end of file
diff --git a/src/components/Costs/CostsFilter.css b/src/components/Costs/CostsFilter.css
new file mode 100644
index 0000000..08ac7e3
--- /dev/null
+++ b/src/components/Costs/CostsFilter.css
@@ -0,0 +1,24 @@
+.costs-filter {
+ color: rgb(11, 0, 2);
+ padding: 0 1rem;
+}
+
+.costs-filter__control {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+ margin: 1rem 0;
+}
+
+.costs-filter label {
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+}
+
+.costs-filter select {
+ font: inherit;
+ padding: 0.5rem 3rem;
+ font-weight: bold;
+ border-radius: 6px
+}
\ No newline at end of file
diff --git a/src/components/Costs/CostsFilter.js b/src/components/Costs/CostsFilter.js
new file mode 100644
index 0000000..df159b4
--- /dev/null
+++ b/src/components/Costs/CostsFilter.js
@@ -0,0 +1,24 @@
+import './CostsFilter.css';
+
+const CostsFilter = (props) => {
+ const yearChangeHandler = (event) => {
+ props.onChangeYear(event.target.value);
+ }
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default CostsFilter;
\ No newline at end of file
diff --git a/src/components/Diagram/DagramBar.css b/src/components/Diagram/DagramBar.css
new file mode 100644
index 0000000..c8e31aa
--- /dev/null
+++ b/src/components/Diagram/DagramBar.css
@@ -0,0 +1,33 @@
+.diagram-bar {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.diagram-bar__inner {
+ height: 100%;
+ width: 100%;
+ border: 1px solid grey;
+ border-radius: 12px;
+ background-color: #fefae1;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+}
+
+.diagram-bar__fill {
+ background-color: #5478ee;
+ width: 100%;
+ transition: all 0.3s ease-out;
+}
+
+.diagram-bar__label {
+ font-weight: bold;
+ font-size: 0.5rem;
+ /* display: flex;
+ justify-content: center; */
+ text-align: center;
+
+}
\ No newline at end of file
diff --git a/src/components/Diagram/Diagram.css b/src/components/Diagram/Diagram.css
new file mode 100644
index 0000000..87efc06
--- /dev/null
+++ b/src/components/Diagram/Diagram.css
@@ -0,0 +1,9 @@
+.diagram{
+ padding: 1rem;
+ border-radius: 12px;
+ background-color: rgb(247, 192, 110);
+ text-align: center;
+ display: flex;
+ justify-content: space-around;
+ height: 10rem;
+}
diff --git a/src/components/Diagram/Diagram.js b/src/components/Diagram/Diagram.js
new file mode 100644
index 0000000..a22b810
--- /dev/null
+++ b/src/components/Diagram/Diagram.js
@@ -0,0 +1,23 @@
+import DiagramBar from "./DiagramBar";
+import "./Diagram.css";
+
+const Diagram = props => {
+ const dataSetsValues = props.dataSets.map((dataSet) => dataSet.value);
+
+ const maxMonthCosts = Math.max(...dataSetsValues);
+
+ return (
+
+ {props.dataSets.map((dataSet) => (
+
+ ))}
+
+ );
+};
+
+export default Diagram;
diff --git a/src/components/Diagram/DiagramBar.js b/src/components/Diagram/DiagramBar.js
new file mode 100644
index 0000000..04baf66
--- /dev/null
+++ b/src/components/Diagram/DiagramBar.js
@@ -0,0 +1,22 @@
+import "./DagramBar.css";
+
+const DiagramBar = props => {
+ let barFillHeight = "0%";
+
+ if (props.maxValue > 0) {
+ barFillHeight = Math.round(props.value / props.maxValue * 100) + "%";
+ }
+
+ return (
+
+ );
+};
+
+export default DiagramBar;
diff --git a/src/components/NewCost/CostForm.css b/src/components/NewCost/CostForm.css
new file mode 100644
index 0000000..c7c6ac6
--- /dev/null
+++ b/src/components/NewCost/CostForm.css
@@ -0,0 +1,49 @@
+.new-cost__control {
+ display: block;
+ margin-bottom: 1rem;
+}
+
+.new-cost__control label {
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+ display: block;
+}
+
+.new-cost__control input {
+ font: inherit;
+ padding: 0.5rem;
+ border-radius: 5px;
+ border: 1px solid white;
+ width: 20rem;
+ max-width: 100%;
+}
+
+.new-cost__actions {
+ text-align: left;
+}
+
+@media (min-width: 580px) {
+ .new-cost__control {
+ display: block;
+ margin-bottom: 1rem;
+ }
+
+ .new-cost__control label {
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+ display: block;
+ }
+
+ .new-cost__control input {
+ font: inherit;
+ padding: 0.5rem;
+ border-radius: 5px;
+ border: 1px solid white;
+ width: 20rem;
+ max-width: 100%;
+ }
+
+ .new-cost__actions {
+ text-align: left;
+ }
+}
diff --git a/src/components/NewCost/CostForm.js b/src/components/NewCost/CostForm.js
new file mode 100644
index 0000000..2c176b0
--- /dev/null
+++ b/src/components/NewCost/CostForm.js
@@ -0,0 +1,94 @@
+import React, {useState} from 'react';
+import './CostForm.css';
+
+
+const CostForm = (props) => {
+ const [inputName, setInputName] = useState('');
+ const [inputAmount, setInputAmount] = useState('');
+ const [inputDate, setInputDate] = useState('');
+
+
+
+ // const [userInput, setUserInput] = useState({
+ // name: '',
+ // amount: '',
+ // date: ''
+ // })
+
+ const nameChangeHandler = (event) => {
+ setInputName(event.target.value)
+ // setUserInput({
+ // ...userInput,
+ // name: event.target.value
+ // })
+ // setUserInput((previousState) => {
+ // return {
+ // ...userInput,
+ // name: event.target.value
+ // }
+ // })
+ }
+
+ const amountChangeHandler = (event) => {
+ setInputAmount(event.target.value)
+ // setUserInput({
+ // ...userInput,
+ // amount: event.target.value
+ // })
+ }
+
+ const dateChangeHandler = (event) => {
+ setInputDate(event.target.value)
+ // setUserInput({
+ // ...userInput,
+ // date: event.target.value
+ // })
+
+ }
+
+ const SubmitHandler = (event) => {
+ event.preventDefault();
+
+ const costData = {
+ description: inputName,
+ amount: inputAmount,
+ date: new Date(inputDate)
+ };
+
+ props.onSaveCostData(costData);
+ setInputName('');
+ setInputAmount('');
+ setInputDate('');
+}
+
+ return (
+
+ );
+}
+
+export default CostForm;
diff --git a/src/components/NewCost/NewCost.css b/src/components/NewCost/NewCost.css
new file mode 100644
index 0000000..5d715e8
--- /dev/null
+++ b/src/components/NewCost/NewCost.css
@@ -0,0 +1,28 @@
+.new-cost {
+ background-color: rgb(244, 109, 109);
+ padding: 1rem;
+ margin: 2rem auto;
+ width: 70rem;
+ max-width: 95%;
+ border-radius: 10px;
+ text-align: center;
+ box-shadow: 0 1px 7px rgba(0, 0, 0, 0.25);
+}
+
+.new-cost button{
+ font: inherit;
+ cursor: pointer;
+ padding: 1rem 2rem;
+ border: 1px solid rgb(109, 67, 67);
+ background-color: rgb(244, 187, 169);
+ color: rgb(76, 127, 244);
+ border-radius: 10px;
+ margin-right: 3px;
+}
+
+.new-cost button:hover,
+.new-cost button:active {
+ background-color: rgb(34, 76, 105);
+ border-color: bisque;
+}
+
diff --git a/src/components/NewCost/NewCost.js b/src/components/NewCost/NewCost.js
new file mode 100644
index 0000000..249d849
--- /dev/null
+++ b/src/components/NewCost/NewCost.js
@@ -0,0 +1,38 @@
+import React, { useState } from "react";
+import CostForm from "./CostForm";
+import "./NewCost.css";
+
+const NewCost = props => {
+ const [isFormVisisble, setIsFormVisible] = useState(false);
+
+ const saveCostDataHandler = inputCostData => {
+ const costData = {
+ ...inputCostData,
+ id: Math.random().toString()
+ };
+ props.onAddCost(costData);
+ setIsFormVisible(false);
+ };
+
+ const inputCostDataHandler = () => {
+ setIsFormVisible(true);
+ };
+
+ const cancelCostHandler = () => {
+ setIsFormVisible(false);
+ };
+
+ return (
+
+ {!isFormVisisble &&
+ }
+ {isFormVisisble &&
+ }
+
+ );
+};
+
+export default NewCost;
diff --git a/src/components/UI/Card.css b/src/components/UI/Card.css
new file mode 100644
index 0000000..084ee0f
--- /dev/null
+++ b/src/components/UI/Card.css
@@ -0,0 +1,4 @@
+.card{
+ border-radius: 10px;
+ box-shadow: 0 1px 7px reba(0 0 0 0.25);
+}
\ No newline at end of file
diff --git a/src/components/UI/Card.js b/src/components/UI/Card.js
new file mode 100644
index 0000000..b79b1e9
--- /dev/null
+++ b/src/components/UI/Card.js
@@ -0,0 +1,8 @@
+import './Card.css';
+
+const Card = (props) => {
+ const classes = 'card ' + props.className;
+ return
{props.children}
;
+}
+
+export default Card;
diff --git a/src/index.css b/src/index.css
index ec2585e..2de6086 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,5 +1,6 @@
body {
- margin: 0;
+ margin: 10px;
+ background-color: bisque;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
diff --git a/src/index.js b/src/index.js
index d563c0f..3606d2c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,17 +1,10 @@
-import React from 'react';
+
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
-import reportWebVitals from './reportWebVitals';
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
-// 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();
+ReactDOM.createRoot(document.getElementById('root'))
+ .render(
+
+)
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 9dfc1c0..0000000
--- a/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
deleted file mode 100644
index 5253d3a..0000000
--- a/src/reportWebVitals.js
+++ /dev/null
@@ -1,13 +0,0 @@
-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;
diff --git a/src/setupTests.js b/src/setupTests.js
deleted file mode 100644
index 8f2609b..0000000
--- a/src/setupTests.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// 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';