6 Commits

Author SHA1 Message Date
3d86f1ae2e Fixed: data validation in offline refuel create form
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-02-03 19:54:58 +01:00
c14bbd1930 Update: diagrams
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-02-03 09:53:19 +01:00
88a78d60e9 Updated: fixed fuel consumption calculation on dashboard
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-02-01 23:35:21 +01:00
4576800e27 Updated: Removed LIMIT for all refuel records, fixed fuel consumption calculation on dashboard
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-02-01 22:56:32 +01:00
652b04bfa1 Updated: Vehicle list hides "Set as default" for default vehicle and improves styling
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-02-01 19:02:24 +01:00
6df1f6574a Updated: Added persistent storage for MariaDB in docker-compose.yaml
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-02-01 18:48:28 +01:00
13 changed files with 129 additions and 64 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -12,7 +12,7 @@ class DashboardController extends Controller {
"mileage" => [], "mileage" => [],
"liters" => [] "liters" => []
]; ];
$raw_data = $default_car ? $refuel->latest_data($default_car['id'], 5) : []; $raw_data = $default_car ? $refuel->latest_data($default_car['id'], 0) : [];
foreach($raw_data as $one) { foreach($raw_data as $one) {
array_push($data['date'], date('d. m.', strtotime($one['created_at']))); array_push($data['date'], date('d. m.', strtotime($one['created_at'])));
array_push($data['price'], $one['price_per_liter']); array_push($data['price'], $one['price_per_liter']);

View File

@@ -46,6 +46,7 @@ class RefuelController extends Controller {
'validationErrors' => $validator->errors() ?: [], 'validationErrors' => $validator->errors() ?: [],
'vehicles' => $vehicles, 'vehicles' => $vehicles,
'title' => 'New refuel record', 'title' => 'New refuel record',
'status' => '400'
]); ]);
return; return;
} }

View File

@@ -38,15 +38,24 @@ class Refuel {
public function latest_data($vehicle_id, $record_count) { public function latest_data($vehicle_id, $record_count) {
try { try {
$stmt = $this->db->prepare(" $sql = "
SELECT `liters`, `price_per_liter`, `total_price`, `mileage`, `created_at` SELECT `liters`, `price_per_liter`, `total_price`, `mileage`, `created_at`
FROM `refueling_records` FROM `refueling_records`
WHERE `vehicle_id` = ? WHERE `vehicle_id` = ?
ORDER BY created_at DESC ORDER BY created_at DESC";
LIMIT ?;
"); if ($record_count > 0) {
$sql .= " LIMIT ?";
}
$stmt = $this->db->prepare($sql);
if ($record_count > 0) {
$stmt->bind_param("ii", $vehicle_id, $record_count);
} else {
$stmt->bind_param("i", $vehicle_id);
}
$stmt->bind_param("ii", $vehicle_id, $record_count);
if ($stmt->execute()) { if ($stmt->execute()) {
$result = $stmt->get_result(); $result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC); $data = $result->fetch_all(MYSQLI_ASSOC);
@@ -62,7 +71,7 @@ class Refuel {
public function latest_one($vehicle_id, $record_count = 1) { public function latest_one($vehicle_id, $record_count = 1) {
try { try {
$stmt = $this->db->prepare(" $sql = "
SELECT SELECT
`r`.`vehicle_id`, `r`.`vehicle_id`,
`v`.`name` AS `vehicle_name`, `v`.`name` AS `vehicle_name`,
@@ -75,11 +84,20 @@ class Refuel {
FROM `refueling_records` AS `r` FROM `refueling_records` AS `r`
JOIN `vehicles` AS `v` ON `r`.`vehicle_id` = `v`.`id` JOIN `vehicles` AS `v` ON `r`.`vehicle_id` = `v`.`id`
WHERE `r`.`vehicle_id` = ? WHERE `r`.`vehicle_id` = ?
ORDER BY `r`.`created_at` DESC ORDER BY `r`.`created_at` DESC";
LIMIT ?;
"); if ($record_count > 0) {
$sql .= " LIMIT ?";
}
$stmt = $this->db->prepare($sql);
if ($record_count > 0) {
$stmt->bind_param("ii", $vehicle_id, $record_count);
} else {
$stmt->bind_param("i", $vehicle_id);
}
$stmt->bind_param("ii", $vehicle_id, $record_count);
if ($stmt->execute()) { if ($stmt->execute()) {
$result = $stmt->get_result(); $result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC); $data = $result->fetch_all(MYSQLI_ASSOC);

View File

@@ -35,7 +35,7 @@ class Vehicle {
} }
public function getVehiclesByUser($user_id) { public function getVehiclesByUser($user_id) {
$stmt = $this->db->prepare("SELECT id, name, registration_plate, fuel_type, note, created_at FROM vehicles WHERE user_id = ?"); $stmt = $this->db->prepare("SELECT id, name, registration_plate, fuel_type, note, is_default, created_at FROM vehicles WHERE user_id = ?");
$stmt->bind_param("i", $user_id); $stmt->bind_param("i", $user_id);
$stmt->execute(); $stmt->execute();
$result = $stmt->get_result(); $result = $stmt->get_result();

View File

@@ -97,26 +97,27 @@
</script> </script>
<script> <script>
const data2 = <?= json_encode($data['date_price_data']); ?>; const data2 = <?= json_encode($data['date_price_data']); ?>;
let cnt_ltr = 0 let cnt_ltr = 0;
let cnt_km = 0 let last_ltr = 0;
let first_km = 0 let last_km = 0;
let first_km = 0;
console.log(data2) for (let i = 0; i < data2['liters'].length; i++) {
if(i === 0) {
first_km = data2['mileage'][i]
continue; // skip bcs were expecting that this was first full tank refuel so this is base value
}
cnt_ltr += data2['liters'][i]
last_ltr = data2['liters'][i]
last_km = data2['mileage'][i]
}
for(let i = 0; i < data2['liters'].length; i++) { // cnt_ltr -= last_ltr // this would be used if we would track consumption from 0km
if(i == 0) { last_km -= first_km
first_km = data2['mileage'][i]
}
cnt_ltr += data2['liters'][i]
cnt_km =+ data2['mileage'][i]
}
console.log("Liters", cnt_ltr, cnt_km, first_km) document.querySelector("#avg-fl-cnsmp").textContent =
console.log("Avg", (cnt_km - first_km) / cnt_ltr) (cnt_ltr/last_km*100).toFixed(1) + " l/100km";
document.querySelector("#avg-fl-cnsmp").textContent = Math.floor((cnt_km - first_km) / cnt_ltr) + " l/100km"
</script> </script>
<script defer src="/js/offline-records.js"></script> <script defer src="/js/offline-records.js"></script>

View File

@@ -14,18 +14,20 @@
<p><?= htmlspecialchars($vehicle['fuel_type']) ?></p> <p><?= htmlspecialchars($vehicle['fuel_type']) ?></p>
<p><?= htmlspecialchars($vehicle['note'] ?? "") ?></p> <p><?= htmlspecialchars($vehicle['note'] ?? "") ?></p>
<div class="actions"> <div class="vehicle-actions">
<br> <br>
<form method="POST" action="/vehicles/delete"> <form method="POST" action="/vehicles/delete">
<input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none"> <input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none">
<input type="submit" value="Delete vehicle" class="btn-danger"> <input type="submit" value="Delete vehicle" class="btn-danger">
</form> </form>
<?php if(!$vehicle['is_default']): ?>
<br> <br>
<form method="POST" action="/vehicles/default"> <form method="POST" action="/vehicles/default">
<input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none"> <input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none">
<input type="submit" value="Set as default" class="btn-primary"> <input type="submit" value="Set as default" class="btn-primary">
</form> </form>
<?php endif; ?>
</div> </div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>

View File

@@ -7,6 +7,11 @@ class View
// Store the data // Store the data
$this->data = $data; $this->data = $data;
// check for status code in data
if(isset($this->data['status'])){
http_response_code($this->data['status']);
}
// Capture the view content // Capture the view content
ob_start(); ob_start();
require_once views . $view . '.php'; require_once views . $view . '.php';

View File

@@ -6,6 +6,8 @@ services:
MARIADB_ROOT_PASSWORD: root MARIADB_ROOT_PASSWORD: root
ports: ports:
- 3306:3306 - 3306:3306
volumes:
- fuelstats_mariadb_data:/var/lib/mysql
networks: networks:
- fuelstats-network - fuelstats-network
profiles: ["prod", "dev"] profiles: ["prod", "dev"]
@@ -42,3 +44,6 @@ services:
networks: networks:
fuelstats-network: fuelstats-network:
driver: bridge driver: bridge
volumes:
fuelstats_mariadb_data:

View File

@@ -27,3 +27,10 @@
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.vehicle-actions {
display: flex;
flex-direction: row;
gap: .5rem;
margin-top: 1rem;
}

View File

@@ -45,11 +45,15 @@ setInterval(async () => {
showDashboard(); showDashboard();
}); });
} }
}
if (localStorage.getItem("refuelOfflineData")) { if (localStorage.getItem("refuelOfflineData")) {
btnOffline.textContent = "Sync data"; Array.from(divActions.children).forEach(
btnOffline.setAttribute("disabled", "disabled"); (el) => (el.style.display = "none"),
} );
btnOffline.style.display = "block";
btnOffline.textContent = "Sync data";
btnOffline.setAttribute("disabled", "disabled");
} }
if (isOnline && !visible) { if (isOnline && !visible) {
@@ -107,7 +111,18 @@ btnOffline.addEventListener("click", async (e) => {
}); });
if (!res.ok) { if (!res.ok) {
throw new Error(`Server error: ${res.statusText}`); if (res.status == 400) {
const html = await res.text();
document.open();
document.write(html);
document.close();
localStorage.removeItem("refuelOfflineData");
return;
} else {
throw new Error(`Server error: ${res.statusText}`);
}
} }
localStorage.removeItem("refuelOfflineData"); localStorage.removeItem("refuelOfflineData");
@@ -150,11 +165,6 @@ btnOffline.addEventListener("click", async (e) => {
</div> </div>
<section class="form"> <section class="form">
<h1 class="header-form">Create offline record</h1> <h1 class="header-form">Create offline record</h1>
<!-- <?php if ($this->get('error')): ?> -->
<!-- <div class="error" style="color: red; margin-bottom: 1rem;"> -->
<!-- <?= htmlspecialchars($this->get('error')) ?> -->
<!-- </div> -->
<!-- <?php endif; ?> -->
<form id="offline_refuel_add"> <form id="offline_refuel_add">
<label for="vehicle">Vehicle</label> <label for="vehicle">Vehicle</label>
<select name="vehicle" id="vehicle"> <select name="vehicle" id="vehicle">
@@ -164,13 +174,7 @@ btnOffline.addEventListener("click", async (e) => {
`<option value="${el.id}">${el.name} | ${el.registration_plate}</option>`, `<option value="${el.id}">${el.name} | ${el.registration_plate}</option>`,
) )
.join("")} .join("")}
<!-- <?php foreach ($this->get('vehicles') as $vehicle): ?> -->
<!-- <option value="<?= $vehicle['id'] ?>"><?= $vehicle['name'] . " | " . $vehicle['registration_plate'] ?></option> -->
<!-- <?php endforeach; ?> -->
</select> </select>
<!-- <?php if (isset($this->get('validationErrors')['vehicle'])): ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['vehicle'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="fuel_type">Fuel type</label> <label for="fuel_type">Fuel type</label>
<select name="fuel_type" id="fuel_type"> <select name="fuel_type" id="fuel_type">
@@ -182,45 +186,67 @@ btnOffline.addEventListener("click", async (e) => {
<option value="Premium Gasoline 98">Premium Gasoline 98</option> <option value="Premium Gasoline 98">Premium Gasoline 98</option>
<option value="Other">Other</option> <option value="Other">Other</option>
</select> </select>
<!-- <?php if (isset($this->get('validationErrors')['fuel_type'])): ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['fuel_type'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="liters">Liters</label> <label for="liters">Liters</label>
<input type="number" name="liters" id="liters" min="0" step=".01" value="0.0"> <input type="number" name="liters" id="liters" min="0" step=".01" value="0.0">
<!-- <?php if (isset($this->get('validationErrors')['liters'])): ?> --> <!-- <small class="error"><?= $this->get('validationErrors')['liters'] ?></small> -->
<!-- <small class="error"><?= $this->get('validationErrors')['liters'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="price_per_liter">Price per liter</label> <label for="price_per_liter">Price per liter</label>
<input type="number" name="price_per_liter" id="price_per_liter" min="0" step=".01" value="0.0"> <input type="number" name="price_per_liter" id="price_per_liter" min="0" step=".01" value="0.0">
<!-- <?php if (isset($this->get('validationErrors')['price_per_liter'])): ?> --> <!-- <small class="error"><?= $this->get('validationErrors')['price_per_liter'] ?></small> -->
<!-- <small class="error"><?= $this->get('validationErrors')['price_per_liter'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="total_price">Total price</label> <label for="total_price">Total price</label>
<input type="number" name="total_price" id="total_price" min="0" step=".01" value="0.0"> <input type="number" name="total_price" id="total_price" min="0" step=".01" value="0.0">
<!-- <?php if (isset($this->get('validationErrors')['total_price'])): ?> --> <!-- <small class="error"><?= $this->get('validationErrors')['total_price'] ?></small> -->
<!-- <small class="error"><?= $this->get('validationErrors')['total_price'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="mileage">Mileage</label> <label for="mileage">Mileage</label>
<input type="number" name="mileage" id="mileage" min="0" step="1" value="0"> <input type="number" name="mileage" id="mileage" min="0" step="1" value="0">
<!-- <?php if (isset($this->get('validationErrors')['mileage'])): ?> --> <!-- <small class="error"><?= $this->get('validationErrors')['mileage'] ?></small> -->
<!-- <small class="error"><?= $this->get('validationErrors')['mileage'] ?></small> -->
<!-- <?php endif; ?> -->
<label for="note">Note</label> <label for="note">Note</label>
<input type="text" name="note" id="note"> <input type="text" name="note" id="note">
<!-- <?php if (isset($this->get('validationErrors')['note'])): ?> --> <!-- <small class="error"><?= $this->get('validationErrors')['note'] ?></small> -->
<!-- <small class="error"><?= $this->get('validationErrors')['note'] ?></small> -->
<!-- <?php endif; ?> -->
<input type="submit" id="btn-offline-submit" value="Create fuel record"> <input type="submit" id="btn-offline-submit" value="Create fuel record">
</form> </form>
</section> </section>
`; `;
document.querySelector("main").appendChild(offline); document.querySelector("main").appendChild(offline);
const inp_lit = document.querySelector("input#liters");
const inp_ppl = document.querySelector("input#price_per_liter");
const inp_tot = document.querySelector("input#total_price");
const rnd = (num) => Math.round((num + Number.EPSILON) * 100) / 100;
function calculate() {
let liters = Number(inp_lit.value);
let price_per_liter = Number(inp_ppl.value);
let total_price = Number(inp_tot.value);
if (price_per_liter > 0 && liters > 0) {
total_price = rnd(liters * price_per_liter);
}
if (price_per_liter > 0 && total_price > 0) {
liters = rnd(total_price / price_per_liter);
}
if (liters > 0 && total_price > 0) {
price_per_liter = rnd(total_price / liters);
}
inp_lit.value = liters;
inp_ppl.value = price_per_liter;
inp_tot.value = total_price;
}
[inp_lit, inp_ppl, inp_tot].forEach((inp) => {
inp.addEventListener("change", () => {
calculate();
});
});
const btnSubmit = document.querySelector("#btn-offline-submit"); const btnSubmit = document.querySelector("#btn-offline-submit");
btnSubmit.addEventListener("click", (e) => { btnSubmit.addEventListener("click", (e) => {
e.preventDefault(); e.preventDefault();