Skip to content

Commit dafb777

Browse files
committed
create app
1 parent e16036d commit dafb777

33 files changed

+1643
-38
lines changed

.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ APP_SECRET=8b2d716c2c4d34c8c6929a5e67c1add4
2020
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
2121
#TRUSTED_HOSTS='^(localhost|example\.com)$'
2222
###< symfony/framework-bundle ###
23+
24+
API_NBP_BASE_URL=https://api.nbp.pl

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ APP_SECRET='$ecretf0rt3st'
44
SYMFONY_DEPRECATIONS_HELPER=999999
55
PANTHER_APP_ENV=panther
66
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
7+
8+
API_NBP_BASE_URL=http://test.nbp.api

assets/css/app.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
11
body {
22
background-color: lightgray;
33
}
4+
5+
.date-picker-container > * {
6+
display: inline-block;
7+
margin: 4px;
8+
}
9+
10+
#date-picker {
11+
background-color: lightgray;
12+
}
13+
14+
.highlighted-rate {
15+
background-color: #bfbfbf;
16+
font-weight: bold;
17+
border: 1px solid #a6c9e2;
18+
}
19+
20+
.error-message {
21+
color: indianred;
22+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import React, {Component} from 'react';
2+
import axios from 'axios';
3+
4+
class ExchangeRates extends Component {
5+
constructor(props) {
6+
super(props);
7+
this.state = {
8+
rates: [],
9+
loading: true,
10+
errorMessage: '',
11+
userDate: '',
12+
latestDate: ''
13+
};
14+
}
15+
16+
getBaseUrl() {
17+
return 'http://telemedi-zadanie.localhost';
18+
}
19+
20+
componentDidMount() {
21+
this.fetchData();
22+
}
23+
24+
getTodaysDate = () => {
25+
return (new Date()).toISOString().split('T')[0];
26+
};
27+
28+
adjustDate(date) {
29+
const inputDate = new Date(date);
30+
const currentHour = new Date().getHours();
31+
if (currentHour < 12) {
32+
inputDate.setDate(inputDate.getDate() - 1);
33+
}
34+
35+
const year = inputDate.getFullYear();
36+
const month = String(inputDate.getMonth() + 1).padStart(2, '0');
37+
const day = String(inputDate.getDate()).padStart(2, '0');
38+
39+
return `${year}-${month}-${day}`;
40+
}
41+
42+
getQueryParamDate(param) {
43+
const searchParams = new URLSearchParams(this.props.location.search);
44+
45+
return searchParams.get(param);
46+
}
47+
48+
handleDateChange = (event) => {
49+
const selectedDate = event.target.value;
50+
this.setState({userDate: selectedDate}, () => {
51+
this.props.history.push({
52+
pathname: '/exchange-rates',
53+
search: `?date=${selectedDate}`,
54+
});
55+
this.fetchData();
56+
});
57+
};
58+
59+
fetchData() {
60+
const todaysDate = this.getTodaysDate();
61+
const ratesDate = this.adjustDate(this.state.userDate || this.getQueryParamDate('date') || todaysDate);
62+
63+
axios.get(`${this.getBaseUrl()}/api/exchange-rates?userDate=${ratesDate}&latestDate=${this.adjustDate(todaysDate)}`)
64+
.then(response => {
65+
if (200 === response.status) {
66+
this.setState({
67+
rates: response.data.rates,
68+
userDate: response.data.userDate,
69+
latestDate: response.data.latestDate
70+
});
71+
} else {
72+
this.setState({userDate: ratesDate, errorMessage: 'Something went wrong.'});
73+
}
74+
})
75+
.catch(error => {
76+
if (error.response.headers && error.response.headers.has('X-Validation-Errors')) {
77+
this.setState({
78+
loading: false,
79+
errorMessage: `Fix following errors and try again: ${error.response.headers.get('X-Validation-Errors')}`
80+
});
81+
} else {
82+
this.setState({userDate: ratesDate, errorMessage: 'Something went wrong.'});
83+
}
84+
})
85+
.finally(() => {
86+
this.setState({loading: false});
87+
})
88+
}
89+
90+
renderTableRows() {
91+
return this.state.rates.map((rate, index) => (
92+
<tr key={index}>
93+
<td><b>{rate.currencyCode}</b> ({rate.currencyName})</td>
94+
<td className="highlighted-rate">{rate.userDateBidRate ?? 'N/A'}</td>
95+
<td className="highlighted-rate">{rate.userDateAskRate ?? 'N/A'}</td>
96+
<td className="highlighted-rate">{rate.userDateNbpRate ?? 'N/A'}</td>
97+
<td>{rate.latestBidRate ?? 'N/A'}</td>
98+
<td>{rate.latestAskRate ?? 'N/A'}</td>
99+
<td>{rate.latestNbpRate ?? 'N/A'}</td>
100+
</tr>
101+
));
102+
}
103+
104+
render() {
105+
const {rates, loading, userDate, latestDate, errorMessage} = this.state;
106+
107+
return (
108+
<div className="container mt-5">
109+
<h2 className="text-center">Exchange Rates @ Telemedi by Adam Guła</h2>
110+
{loading ? (
111+
<div className="text-center">
112+
<span className="fa fa-spin fa-spinner fa-4x"></span>
113+
</div>
114+
) : (
115+
<>
116+
<div className="text-center mb-4 date-picker-container">
117+
<label htmlFor="date-picker">Selected date</label>
118+
<input
119+
id="date-picker"
120+
type="date"
121+
value={userDate}
122+
min="2023-01-01"
123+
max={this.getTodaysDate()}
124+
onChange={this.handleDateChange}
125+
/>
126+
</div>
127+
<table className="table table-bordered">
128+
<thead>
129+
<tr>
130+
<th rowSpan="1"></th>
131+
<th colSpan="3" className="text-center highlighted-rate">Rates for selected date
132+
({userDate})
133+
</th>
134+
<th colSpan="3" className="text-center">Latest rates ({latestDate})</th>
135+
</tr>
136+
<tr>
137+
<th>Currency</th>
138+
<th className="highlighted-rate">Bid</th>
139+
<th className="highlighted-rate">Ask</th>
140+
<th className="highlighted-rate">NBP</th>
141+
<th>Bid</th>
142+
<th>Ask</th>
143+
<th>NBP</th>
144+
</tr>
145+
</thead>
146+
<tbody>
147+
{rates.length > 0 ? this.renderTableRows() : (
148+
<tr>
149+
{errorMessage.length > 0 ? (
150+
<td colSpan="6" className="text-center error-message">{errorMessage}</td>) : (
151+
<td colSpan="6" className="text-center">No data available</td>)}
152+
</tr>
153+
)}
154+
155+
</tbody>
156+
</table>
157+
</>
158+
)}
159+
</div>
160+
);
161+
}
162+
}
163+
164+
export default ExchangeRates;

assets/js/components/Home.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
// ./assets/js/components/Home.js
2-
31
import React, {Component} from 'react';
42
import {Route, Redirect, Switch, Link} from 'react-router-dom';
53
import SetupCheck from "./SetupCheck";
4+
import ExchangeRates from "./ExchangeRates";
65

76
class Home extends Component {
87

@@ -16,13 +15,16 @@ class Home extends Component {
1615
<li className="nav-item">
1716
<Link className={"nav-link"} to={"/setup-check"}> React Setup Check </Link>
1817
</li>
19-
18+
<li className="nav-item">
19+
<Link className={"nav-link"} to={"/exchange-rates"}> Exchange Rates </Link>
20+
</li>
2021
</ul>
2122
</div>
2223
</nav>
2324
<Switch>
24-
<Redirect exact from="/" to="/setup-check" />
25+
<Redirect exact from="/" to="/setup-check"/>
2526
<Route path="/setup-check" component={SetupCheck} />
27+
<Route path="/exchange-rates" component={ExchangeRates} />
2628
</Switch>
2729
</div>
2830
)

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"name": "telemedi/test-purposes",
33
"autoload": {
44
"psr-4": {
5-
"App\\": "src/App/"
5+
"App\\": "src/App/",
6+
"App\\Tests\\": "tests/"
67
},
78
"classmap": [
89
"src/Kernel.php"

config/routes.yaml

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
#home:
2-
# path: /
3-
# defaults: { _controller: 'AppBundle\Controller\DefaultController::indexAction' }
4-
# methods: GET
5-
#
6-
71
setupcheck:
8-
path: /api/setup-check
9-
controller: App\Controller\DefaultController::setupCheck
2+
path: /api/setup-check
3+
controller: App\Presentation\Controller\DefaultController::setupCheck
4+
5+
exchangerates:
6+
path: /api/exchange-rates
7+
controller: App\Presentation\Controller\ExchangeRatesController
108

11-
index:
12-
path: /{wildcard}
13-
defaults: {
14-
_controller: App\Controller\DefaultController::index
15-
}
16-
requirements:
17-
wildcard: .*
18-
# controller: App\Controller\DefaultController::index
199

10+
ndex:
11+
path: /{wildcard}
12+
defaults: {
13+
_controller: App\Presentation\Controller\DefaultController::index
14+
}
15+
requirements:
16+
wildcard: .*

config/services.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ services:
2121

2222
# controllers are imported separately to make sure services can be injected
2323
# as action arguments even if you don't extend any base controller class
24-
App\Controller\:
25-
resource: '../src/App/Controller/'
24+
App\Presentation\Controller\:
25+
resource: '../src/App/Presentation/Controller/'
2626
autowire: true
2727
tags: ['controller.service_arguments']
2828

2929
# add more service definitions when explicit configuration is needed
3030
# please note that last definitions always *replace* previous ones
31+
32+
App\Infrastruture\Api\NBP\NbpApi:
33+
arguments:
34+
$baseUrl: '%env(API_NBP_BASE_URL)%'

config/services_test.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
Symfony\Component\HttpClient\MockHttpClient:
3+
public: true
4+
arguments:
5+
- [ ]
6+
7+
App\Infrastruture\Api\NBP\NbpApi:
8+
arguments:
9+
$client: '@Symfony\Component\HttpClient\MockHttpClient'
10+
$logger: '@logger'
11+
$baseUrl: '%env(API_NBP_BASE_URL)%'

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)