diff --git a/actions-web.php b/actions-web.php
index 5a7a8ccd..0c70dfa6 100644
--- a/actions-web.php
+++ b/actions-web.php
@@ -84,32 +84,6 @@ function listbikes($stand)
response($bicycles, 0, array('notes' => $notes, 'stacktopbike' => $stacktopbike), 0);
}
-function liststands()
-{
- global $db;
-
- response(_('not implemented'), 0, '', 0);
- exit;
- $result = $db->query('SELECT standId,standName,standDescription,standPhoto,serviceTag,placeName,longitude,latitude FROM stands ORDER BY standName');
- while ($row = $result->fetch_assoc()) {
- $bikenum = $row['bikeNum'];
- $result2 = $db->query("SELECT note FROM notes WHERE bikeNum='$bikenum' AND deleted IS NULL ORDER BY time DESC");
- $note = '';
- while ($row = $result2->fetch_assoc()) {
- $note .= $row['note'] . '; ';
- }
- $note = substr($note, 0, strlen($note) - 2); // remove last two chars - comma and space
- if ($note) {
- $bicycles[] = '*' . $bikenum; // bike with note / issue
- $notes[] = $note;
- } else {
- $bicycles[] = $bikenum;
- $notes[] = '';
- }
- }
- response($stands, 0, '', 0);
-}
-
function removenote($userId, $bikeNum)
{
global $db;
@@ -301,30 +275,6 @@ function trips($userId, $bike = 0)
echo json_encode($jsoncontent); // TODO change to response function
}
-function getuserstats()
-{
- global $db;
- $result = $db->query('SELECT users.userId,username,count(action) AS count FROM users LEFT JOIN history ON users.userId=history.userId WHERE history.userId IS NOT NULL GROUP BY username ORDER BY count DESC');
- while ($row = $result->fetch_assoc()) {
- $result2 = $db->query("SELECT count(action) AS rentals FROM history WHERE action='RENT' AND userId=" . $row['userId']);
- $row2 = $result2->fetch_assoc();
- $result2 = $db->query("SELECT count(action) AS returns FROM history WHERE action='RETURN' AND userId=" . $row['userId']);
- $row3 = $result2->fetch_assoc();
- $jsoncontent[] = array('userid' => $row['userId'], 'username' => $row['username'], 'count' => $row['count'], 'rentals' => $row2['rentals'], 'returns' => $row3['returns']);
- }
- echo json_encode($jsoncontent); // TODO change to response function
-}
-
-function getusagestats()
-{
- global $db;
- $result = $db->query("SELECT count(action) AS count,DATE(time) AS day,action FROM history WHERE userId IS NOT NULL AND action IN ('RENT','RETURN') GROUP BY day,action ORDER BY day DESC LIMIT 60");
- while ($row = $result->fetch_assoc()) {
- $jsoncontent[] = array('day' => $row['day'], 'count' => $row['count'], 'action' => $row['action']);
- }
- echo json_encode($jsoncontent); // TODO change to response function
-}
-
function validatecoupon($userid, $coupon)
{
global $db, $creditSystem;
diff --git a/command.php b/command.php
index 6a23acb4..81f16bc4 100644
--- a/command.php
+++ b/command.php
@@ -100,24 +100,6 @@
checkbikeno($bikeno);
revert($userid,$bikeno);
break;
- case "stands": #"operationId": "stand.get",
- logrequest($userid,$action);
- $auth->refreshSession();
- checkprivileges($userid);
- liststands();
- break;
- case "userstats":
- logrequest($userid,$action);
- $auth->refreshSession();
- checkprivileges($userid);
- getuserstats();
- break;
- case "usagestats":
- logrequest($userid,$action);
- $auth->refreshSession();
- checkprivileges($userid);
- getusagestats();
- break;
case "trips":
logrequest($userid,$action);
$auth->refreshSession();
diff --git a/config/routes.php b/config/routes.php
index 4e6545d0..ba2005e2 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -8,9 +8,9 @@
$routes->add('command', '/command.php')
->controller([\BikeShare\Controller\CommandController::class, 'index']);
$routes->add('scan', '/scan.php/{action}/{id}')
+ ->requirements(['id' => '\d+'])
+ ->requirements(['action' => 'rent|return'])
->controller([\BikeShare\Controller\ScanController::class, 'index']);
- $routes->add('admin_old', '/admin.php')
- ->controller([\BikeShare\Controller\AdminController::class, 'index']);
$routes->add('admin', '/admin')
->controller([\BikeShare\Controller\AdminController::class, 'index']);
$routes->add('register', '/register.php')
@@ -28,14 +28,19 @@
$routes->add('reset_password', '/resetPassword')
->controller([\BikeShare\Controller\SecurityController::class, 'resetPassword']);
+ $routes->add('api_stand_index', '/api/stand')
+ ->methods(['GET'])
+ ->controller([\BikeShare\Controller\Api\StandController::class, 'index']);
$routes->add('api_bike_index', '/api/bike')
->methods(['GET'])
->controller([\BikeShare\Controller\Api\BikeController::class, 'index']);
$routes->add('api_bike_item', '/api/bike/{bikeNumber}')
+ ->requirements(['bikeNumber' => '\d+'])
->methods(['GET'])
->controller([\BikeShare\Controller\Api\BikeController::class, 'item']);
$routes->add('api_bike_last_usage', '/api/bikeLastUsage/{bikeNumber}')
->methods(['GET'])
+ ->requirements(['bikeNumber' => '\d+'])
->controller([\BikeShare\Controller\Api\BikeController::class, 'lastUsage']);
$routes->add('api_coupon_index', '/api/coupon')
->methods(['GET'])
@@ -51,15 +56,27 @@
->controller([\BikeShare\Controller\Api\UserController::class, 'index']);
$routes->add('api_user_item', '/api/user/{userId}')
->methods(['GET'])
+ ->requirements(['userId' => '\d+'])
->controller([\BikeShare\Controller\Api\UserController::class, 'item']);
$routes->add('api_user_item_update', '/api/user/{userId}')
->methods(['PUT'])
+ ->requirements(['userId' => '\d+'])
->controller([\BikeShare\Controller\Api\UserController::class, 'update']);
$routes->add('api_credit_add', '/api/credit')
->methods(['PUT'])
->controller([\BikeShare\Controller\Api\CreditController::class, 'add']);
+ $routes->add('api_report_daily', '/api/report/daily')
+ ->methods(['GET'])
+ ->controller([\BikeShare\Controller\Api\ReportController::class, 'daily']);
+ $routes->add('api_report_users', '/api/report/user/{year}')
+ ->methods(['GET'])
+ ->defaults(['year' => date('Y')])
+ ->requirements(['year' => '\d+'])
+ ->controller([\BikeShare\Controller\Api\ReportController::class, 'user']);
$routes->add('personal_stats_year', '/personalStats/year/{year}')
->methods(['GET'])
+ ->defaults(['year' => date('Y')])
+ ->requirements(['year' => '\d+'])
->controller([\BikeShare\Controller\PersonalStatsController::class, 'yearStats']);
};
\ No newline at end of file
diff --git a/public/js/admin.js b/public/js/admin.js
index 9eb2af72..47b78d17 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -69,7 +69,7 @@ $(document).ready(function () {
stands();
break;
case "#reports":
- userstats();
+ usagestats();
break;
case "#credit":
if (window.ga) ga('send', 'event', 'buttons', 'click', 'admin-couponlist');
@@ -189,12 +189,53 @@ function last(bikeNumber) {
});
}
-function stands() {
+function generateStandCards(data) {
+ const $container = $("#standsconsole");
+ const $template = $("#stand-card-template");
+ $container.empty();
+
+ $.each(data, function (index, item) {
+ const $card = $template.clone().removeAttr("id").removeClass("d-none");
+
+ $card.find(".stand-name").text(item.standName);
+
+ const $photo = $card.find(".stand-photo");
+ if (item.standPhoto) {
+ $photo.attr("src", item.standPhoto).removeClass("d-none");
+ } else {
+ $photo.addClass("d-none");
+ }
+
+ $card.find(".stand-description").text(item.standDescription);
+
+ if (parseInt(item.latitude) !== 0 && parseInt(item.longitude) !== 0) {
+ const googleMapsUrl = `https://www.google.com/maps?q=${item.latitude},${item.longitude}`;
+ $card.find(".stand-location")
+ .removeClass("d-none")
+ .attr("href", googleMapsUrl);
+ }
+
+ if (item.standName.toLowerCase().includes("servis")) {
+ $card.find(".service-stand").removeClass("d-none");
+ } else if (item.standName.toLowerCase().includes("zruseny")) {
+ $card.find(".removed-stand").removeClass("d-none");
+ }
+
+ $container.append($card);
+ });
+}
+
+function stands(standId) {
$.ajax({
- url: "command.php?action=stands"
- }).done(function (jsonresponse) {
- jsonobject = $.parseJSON(jsonresponse);
- handleresponse("standsconsole", jsonobject);
+ url: "/api/stand" + (standId ? "/" + standId : ""),
+ method: "GET",
+ dataType: "json",
+ success: function(response) {
+ generateStandCards(response);
+ },
+ error: function(xhr, status, error) {
+ console.error("Error fetching stand data:", error);
+ }
});
}
@@ -207,7 +248,9 @@ function userlist() {
dataSrc: '',
cache: true
},
- dom: 'lrtip',
+ layout: {
+ topEnd: null //disable default searchField
+ },
columns: [
{
data: 'username',
@@ -220,15 +263,18 @@ function userlist() {
},
{
data: 'privileges',
- name: 'privileges'
+ name: 'privileges',
+ type: 'num'
},
{
data: 'userLimit',
- name: 'userLimit'
+ name: 'userLimit',
+ type: 'num'
},
{
data: 'credit',
name: 'credit',
+ type: 'num-fmt',
visible: creditenabled === 1,
render: function(data, type, row) {
return `${data} ${creditcurrency}`;
@@ -260,37 +306,73 @@ function userlist() {
}
function userstats() {
- var code = "";
- $.ajax({
- url: "command.php?action=userstats"
- }).done(function (jsonresponse) {
- jsonobject = $.parseJSON(jsonresponse);
- if (jsonobject.length > 0) code = '
User Actions Rentals Returns ';
- for (var i = 0, len = jsonobject.length; i < len; i++) {
- code = code + '' + jsonobject[i]["username"] + ' ' + jsonobject[i]["count"] + ' ' + jsonobject[i]["rentals"] + ' ' + jsonobject[i]["returns"] + ' ';
+ $('#report-daily-table').addClass('d-none').closest('#stats-report-table_wrapper').addClass('d-none');
+ $('#report-user-year').removeClass('d-none');
+ let table = $('#report-user-table').removeClass('d-none').DataTable({
+ destroy: true,
+ paging: false,
+ info: false,
+ searching: false,
+ ajax: {
+ url: '/api/report/user/',
+ dataSrc: '',
+ cache: true,
+ },
+ order: [[3, 'desc']],
+ columns: [
+ {
+ data: 'username',
+ name: 'username',
+ },
+ {
+ data: 'rentCount',
+ },
+ {
+ data: 'returnCount',
+ },
+ {
+ data: 'totalActionCount',
+ }
+ ],
+ error: function(xhr, error, code) {
+ console.error('Error loading daily report data:', error);
}
- if (jsonobject.length > 0) code = code + '
';
- $('#reportsconsole').html(code);
- $('#userstatstable').dataTable({
- "paging": false,
- "ordering": false,
- "info": false
- });
+ });
+
+ $('#year').on('change', function() {
+ table.ajax.url('/api/report/user/' + $('#year').val());
+ table.ajax.reload();
});
}
function usagestats() {
- var code = "";
- $.ajax({
- url: "command.php?action=usagestats"
- }).done(function (jsonresponse) {
- jsonobject = $.parseJSON(jsonresponse);
- if (jsonobject.length > 0) code = 'Day Action Count ';
- for (var i = 0, len = jsonobject.length; i < len; i++) {
- code = code + '' + jsonobject[i]["day"] + ' ' + jsonobject[i]["action"] + ' ' + jsonobject[i]["count"] + ' ';
+ $('#report-user-table').addClass('d-none').closest('#report-user-table_wrapper').addClass('d-none');
+ $('#report-user-year').addClass('d-none');
+ $('#report-daily-table').removeClass('d-none').DataTable({
+ destroy: true,
+ paging: false,
+ info: false,
+ searching: false,
+ ajax: {
+ url: '/api/report/daily',
+ dataSrc: '',
+ cache: true,
+ },
+ order: [[0, 'desc']],
+ columns: [
+ {
+ data: 'day',
+ },
+ {
+ data: 'rentCount',
+ },
+ {
+ data: 'returnCount',
+ }
+ ],
+ error: function(xhr, error, code) {
+ console.error('Error loading user report data:', error);
}
- if (jsonobject.length > 0) code = code + '
';
- $('#reportsconsole').html(code);
});
}
diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php
index b0c1f65f..6fa2da24 100644
--- a/src/Controller/AdminController.php
+++ b/src/Controller/AdminController.php
@@ -37,6 +37,7 @@ public function index(
[
'configuration' => $configuration,
'creditSystem' => $creditSystem,
+ 'currentYear' => date('Y'),
]
);
}
diff --git a/src/Controller/Api/BikeController.php b/src/Controller/Api/BikeController.php
index 04c38e4b..1d7cce52 100644
--- a/src/Controller/Api/BikeController.php
+++ b/src/Controller/Api/BikeController.php
@@ -36,7 +36,7 @@ public function index(
}
/**
- * @Route("/api/bike/{bikeNumber}", name="api_bike_item", methods={"GET"})
+ * @Route("/api/bike/{bikeNumber}", name="api_bike_item", methods={"GET"}, requirements: {"bikeNumber"="\d+"})
*/
public function item(
$bikeNumber,
diff --git a/src/Controller/Api/ReportController.php b/src/Controller/Api/ReportController.php
new file mode 100644
index 00000000..ef19443a
--- /dev/null
+++ b/src/Controller/Api/ReportController.php
@@ -0,0 +1,68 @@
+isGranted('ROLE_ADMIN')) {
+ $logger->info(
+ 'User tried to access admin page without permission',
+ [
+ 'user' => $this->getUser()->getUserIdentifier(),
+ ]
+ );
+
+ return $this->json([], Response::HTTP_FORBIDDEN);
+ }
+
+ $stats = $historyRepository->dailyStats();
+
+ return $this->json($stats);
+ }
+ /**
+ * @Route("/report/user/{year}", name="api_report_user", , requirements: ['year' => '\d+'] methods={"GET"})
+ */
+ public function user(
+ $year = 1900,
+ HistoryRepository $historyRepository,
+ LoggerInterface $logger
+ ): Response {
+ if (!$this->isGranted('ROLE_ADMIN')) {
+ $logger->info(
+ 'User tried to access admin page without permission',
+ [
+ 'user' => $this->getUser()->getUserIdentifier(),
+ ]
+ );
+
+ return $this->json([], Response::HTTP_FORBIDDEN);
+ }
+ if ($year === 1900) {
+ $year = (int)date('Y');
+ } elseif (
+ $year > (int)date('Y')
+ || $year < 2010
+ ) {
+ return $this->json([], Response::HTTP_BAD_REQUEST);
+ }
+
+ $stats = $historyRepository->userStats((int)$year);
+
+ return $this->json($stats);
+ }
+}
diff --git a/src/Controller/Api/StandController.php b/src/Controller/Api/StandController.php
new file mode 100644
index 00000000..0d6ece7e
--- /dev/null
+++ b/src/Controller/Api/StandController.php
@@ -0,0 +1,38 @@
+isGranted('ROLE_ADMIN')) {
+ $logger->info(
+ 'User tried to access admin page without permission',
+ [
+ 'user' => $this->getUser()->getUserIdentifier(),
+ ]
+ );
+
+ return $this->json([], Response::HTTP_FORBIDDEN);
+ }
+
+ $bikes = $standRepository->findAll();
+
+ return $this->json($bikes);
+ }
+}
diff --git a/src/Controller/Api/UserController.php b/src/Controller/Api/UserController.php
index 3d9af52a..8a43e157 100644
--- a/src/Controller/Api/UserController.php
+++ b/src/Controller/Api/UserController.php
@@ -38,7 +38,7 @@ public function index(
}
/**
- * @Route("/api/user/{userId}", name="api_user_item", methods={"GET"})
+ * @Route("/api/user/{userId}", name="api_user_item", methods={"GET"}, requirements: {"userId"="\d+"})
*/
public function item(
$userId,
@@ -66,7 +66,7 @@ public function item(
}
/**
- * @Route("/api/user/{userId}", name="api_user_item_update", methods={"PUT"})
+ * @Route("/api/user/{userId}", name="api_user_item_update", methods={"PUT"}, requirements: {"userId"="\d+"})
*/
public function update(
$userId,
diff --git a/src/Controller/PersonalStatsController.php b/src/Controller/PersonalStatsController.php
index 4a7d6135..ff2ac09f 100644
--- a/src/Controller/PersonalStatsController.php
+++ b/src/Controller/PersonalStatsController.php
@@ -14,14 +14,23 @@
class PersonalStatsController extends AbstractController
{
/**
- * @Route("/personalStats/year/{year}", name="personal_stats_year", methods={"GET"})
+ * @Route("/personalStats/year/{year}", name="personal_stats_year", methods={"GET"}, requirements: {"year"="\d+"})
*/
public function yearStats(
- $year,
+ $year = 1900,
StatsRepository $statsRepository,
StandRepository $standRepository,
User $user
): Response {
+ if ($year === 1900) {
+ $year = (int)date('Y');
+ } elseif (
+ $year > (int)date('Y')
+ || $year < 2010
+ ) {
+ return new Response('', Response::HTTP_BAD_REQUEST);
+ }
+
$userId = $user->findUserIdByNumber($this->getUser()->getUserIdentifier());
$stats = $statsRepository->getUserStatsForYear((int)$userId, (int)$year);
$stands = $standRepository->findAll();
diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php
index f4907a6f..0f162b64 100644
--- a/src/Controller/ScanController.php
+++ b/src/Controller/ScanController.php
@@ -6,6 +6,7 @@
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Annotation\Route;
class ScanController extends AbstractController
{
@@ -17,7 +18,7 @@ public function __construct(Kernel $kernel)
}
/**
- * @Route("/scan.php/{action}/{id}", name="scan")
+ * @Route("/scan.php/{action}/{id}", name="scan", requirements={"action"="rent|return"}, requirements: {"id"="\d+"})
*/
public function index(
Request $request
diff --git a/src/EventListener/ControllerEventListener.php b/src/EventListener/ControllerEventListener.php
index 88326461..d3f343af 100644
--- a/src/EventListener/ControllerEventListener.php
+++ b/src/EventListener/ControllerEventListener.php
@@ -11,6 +11,7 @@
class ControllerEventListener
{
private const LOGGED_ROUTES = [
+ 'api_stand_index',
'api_bike_index',
'api_bike_item',
'api_bike_last_usage',
@@ -21,6 +22,8 @@ class ControllerEventListener
'api_user_item',
'api_user_item_update',
'api_credit_add',
+ 'api_report_daily',
+ 'api_report_user',
];
private DbInterface $db;
diff --git a/src/Repository/HistoryRepository.php b/src/Repository/HistoryRepository.php
index 879c084f..177cfe35 100644
--- a/src/Repository/HistoryRepository.php
+++ b/src/Repository/HistoryRepository.php
@@ -32,4 +32,42 @@ public function addItem(
VALUES ($userId, $bikeNum, '$action', '$parameter')
");
}
+
+ public function dailyStats(): array
+ {
+ $result = $this->db->query(
+ "SELECT
+ DATE(time) AS day,
+ SUM(CASE WHEN action = 'RENT' THEN 1 ELSE 0 END) AS rentCount,
+ SUM(CASE WHEN action = 'RETURN' THEN 1 ELSE 0 END) AS returnCount
+ FROM history
+ WHERE userId IS NOT NULL
+ AND action IN ('RENT','RETURN')
+ GROUP BY day
+ ORDER BY day DESC
+ LIMIT 60
+ ")->fetchAllAssoc();
+
+ return $result;
+ }
+
+ public function userStats(int $year): array
+ {
+ $result = $this->db->query(
+ "SELECT
+ users.userId,
+ username,
+ SUM(CASE WHEN action = 'RENT' THEN 1 ELSE 0 END) AS rentCount,
+ SUM(CASE WHEN action = 'RETURN' THEN 1 ELSE 0 END) AS returnCount,
+ COUNT(action) AS totalActionCount
+ FROM users
+ LEFT JOIN history ON users.userId=history.userId
+ WHERE history.userId IS NOT NULL
+ AND YEAR(time) = " . $year . "
+ GROUP BY username
+ ORDER BY totalActionCount DESC
+ ")->fetchAllAssoc();
+
+ return $result;
+ }
}
diff --git a/src/Repository/StandRepository.php b/src/Repository/StandRepository.php
index 1b4fe732..0a1c1428 100644
--- a/src/Repository/StandRepository.php
+++ b/src/Repository/StandRepository.php
@@ -28,7 +28,8 @@ public function findAll(): array
placeName,
longitude,
latitude
- FROM stands'
+ FROM stands
+ ORDER BY standName'
)->fetchAllAssoc();
$stands = [];
diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig
index 6cb8977c..1ce06650 100644
--- a/templates/admin/index.html.twig
+++ b/templates/admin/index.html.twig
@@ -71,11 +71,7 @@
{% include ('admin/fleet.html.twig') %}
-
+ {% include ('admin/stands.html.twig') %}
{% include ('admin/user.html.twig') %}
@@ -86,17 +82,7 @@
{% endif %}
-
-
-
- {{ 'Daily stats'|trans }}
-
-
- {{ 'User stats'|trans }}
-
-
-
-
+ {% include ('admin/report.html.twig') %}
diff --git a/templates/admin/report.html.twig b/templates/admin/report.html.twig
new file mode 100644
index 00000000..99e00e93
--- /dev/null
+++ b/templates/admin/report.html.twig
@@ -0,0 +1,56 @@
+{% block report %}
+
+
+
+ {{ 'Daily stats'|trans }}
+
+
+ {{ 'User stats'|trans }}
+
+
+
+
+
+
+
+ {{ 'Day'|trans }}
+ {{ 'Rent'|trans }}
+ {{ 'Return'|trans }}
+
+
+
+
+
+
+
+
+
+ {{ 'User'|trans }}
+ {{ 'Rents'|trans }}
+ {{ 'Returns'|trans }}
+ {{ 'Total Actions'|trans }}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/admin/stands.html.twig b/templates/admin/stands.html.twig
new file mode 100644
index 00000000..59271548
--- /dev/null
+++ b/templates/admin/stands.html.twig
@@ -0,0 +1,29 @@
+{% block stands %}
+
+
+
+
+
+ {# stand card#}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/admin/user.edit.html.twig b/templates/admin/user.edit.html.twig
new file mode 100644
index 00000000..5c125c0d
--- /dev/null
+++ b/templates/admin/user.edit.html.twig
@@ -0,0 +1,38 @@
+{% block user_edit %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/templates/admin/user.html.twig b/templates/admin/user.html.twig
index 44b8401d..cb119688 100644
--- a/templates/admin/user.html.twig
+++ b/templates/admin/user.html.twig
@@ -1,40 +1,5 @@
{% block user %}
-
+ {% include ('admin/user.edit.html.twig') %}
diff --git a/translations/messages+intl-icu.en.php b/translations/messages+intl-icu.en.php
index da3dc585..57440d2d 100644
--- a/translations/messages+intl-icu.en.php
+++ b/translations/messages+intl-icu.en.php
@@ -15,6 +15,7 @@
'Login' => 'Login',
'Keep me logged in' => 'Keep me logged in',
'Choose bike number and rent bicycle. You will receive a code to unlock the bike and the new code to set.' => 'Choose bike number and rent bicycle. You will receive a code to unlock the bike and the new code to set.',
+ 'Day' => 'Day',
'Rent' => 'Rent',
'Describe problem' => 'Describe problem',
'Return this bicycle to the selected stand.' => 'Return this bicycle to the selected stand.',
@@ -387,4 +388,9 @@
=0 { day }
=1 { days }
}',
+ 'Rents' => 'Rents',
+ 'Returns' => 'Returns',
+ 'Total Actions' => 'Total Actions',
+ 'Year' => 'Year',
+ 'View on Google Maps' => 'View on Google Maps',
];