14 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
7517bcb78f Updated: Improved vehicle and refuel data handling on dashboard
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 14s
Updated:
- DashboardController now fetches latest refuel record only for the default vehicle
- VehicleController - first created vehicle is now set as default automatically
- Refuel model - latest_one() now accepts vehicle_id instead of user_id
- Dashboard view - improved handling when no vehicles or refuel records exist
- CSS styles - adjusted dashboard layout and global action padding
2025-02-01 18:38:49 +01:00
f90c707435 Updated: docker-compose.yml networking for proper DB access 2025-02-01 17:35:22 +01:00
60423d37ce Updated: TODO.md
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-01-30 14:39:21 +01:00
5989fba225 Fix: unable to register
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 22s
2025-01-27 02:07:01 +01:00
ba11c41147 Added: mileage, average fuel consumption
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-01-27 01:57:31 +01:00
64c7fd15a1 Added: set default vehicle 2025-01-27 00:38:22 +01:00
ea3afa2507 Updated: README.md added diagrams
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-01-26 23:16:00 +01:00
a5f99788fc Added: Delete vehicle 2025-01-26 23:01:35 +01:00
22 changed files with 360 additions and 78 deletions

BIN
.screenshots/class.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
.screenshots/dlm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
.screenshots/usecase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -53,5 +53,13 @@ php -S localhost:8000 -t ./public
4. Track your fuel consumption and spending through the dashboard.
5. View detailed stats and graphs to analyze your driving habits.
## Use case diagram
![](.screenshots/usecase.png)
## Data logical model
![](.screenshots/dlm.png)
## Class diagram
![](.screenshots/class.png)
## License
This project is licensed under GPL3.0 and later. More information is available in the `LICENSE` file.

29
TODO.md
View File

@@ -7,17 +7,38 @@
## Core of the app
- [ ] intro tutorial when no car exist or just dont show anything
- [ ] change/set default car
- [x] change/set default car
- [ ] specific car view - charts, fuel records
- [ ] remove/edit fuel record
## Until release
- [ ] Sync offline data from locale storage
- [ ] Include kilometer state of an car
- [x] Sync offline data from locale storage
- [x] Include kilometer state of an car
- [ ] More charts
- [ ] Average fuel conusption
- [x] Average fuel conusption
- [ ] Kilometer state
- [ ] More cards
- [ ] Average fuel conusption in last 30 days
- [ ] Kilometer state in last 30 days`
- [ ] Offline navigation between dashboard and offline form
## What has to be done
- [x] Vehicle delete
- [ ] intro tutorial when no car exist or just dont show anything
- [x] change/set default car
- [x] hide errors
## Nice to have
- [ ] specific car view - charts, fuel records
- [ ] remove/edit fuel record
- [x] Include kilometer state of an car
- [ ] More charts
- [x] Average fuel conusption
- [ ] Kilometer state
- [ ] More cards
- [ ] Average fuel conusption in last 30 days
- [ ] Kilometer state in last 30 days`
- [ ] Offline navigation between dashboard and offline form
- [ ] Fix vehicle deletion - wrong redirect
- [ ] Update diagrams in README.md

View File

@@ -3,29 +3,25 @@ class DashboardController extends Controller {
public function index() {
$vehicle = new Vehicle();
$vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
$default_car = $vehicle->getDefaultVehicle($_SESSION['user']['id']);
$default_car = $vehicle->getDefaultVehicle($_SESSION['user']['id']) ?? null;
$refuel = new Refuel();
$data = [
"date" => [],
"price" => [],
"mileage" => [],
"liters" => []
];
$raw_data = $refuel->latest_data($default_car['id'], 5);
$raw_data = $default_car ? $refuel->latest_data($default_car['id'], 0) : [];
foreach($raw_data as $one) {
array_push($data['date'], date('d. m.', strtotime($one['created_at'])));
array_push($data['price'], $one['price_per_liter']);
array_push($data['mileage'], $one['mileage']);
array_push($data['liters'], $one['liters']);
}
$latest_record = [
'name',
'liters',
'price_per_liter',
'total_price',
'created_at'
];
$latest_record = $refuel->latest_one($_SESSION['user']['id'])[0];
$latest_data = $default_car ? $refuel->latest_one($default_car['id']) : [];
$latest_record = !empty($latest_data) ? $latest_data[0] : null;
$this->view('dashboard/index', [
'title' => 'Dashboard',

View File

@@ -18,6 +18,7 @@ class RefuelController extends Controller {
$liters = $_POST['liters'] ?? '';
$price_per_liter = $_POST['price_per_liter'] ?? '';
$total_price = $_POST['total_price'] ?? '';
$mileage = $_POST['mileage'] ?? '';
$note = $_POST['note'] ?? '';
$validator = new Validator();
@@ -29,6 +30,7 @@ class RefuelController extends Controller {
$validator->number('liters', $liters);
$validator->number('price_per_liter', $price_per_liter);
$validator->number('total_price', $total_price);
$validator->number('mileage', $mileage);
if (round($liters * $price_per_liter, 2) != $total_price) {
$validator->setErrors(["total_price" => "Price calculation is wrong"]);
@@ -44,6 +46,7 @@ class RefuelController extends Controller {
'validationErrors' => $validator->errors() ?: [],
'vehicles' => $vehicles,
'title' => 'New refuel record',
'status' => '400'
]);
return;
}
@@ -57,6 +60,7 @@ class RefuelController extends Controller {
'liters' => $liters,
'price_per_liter' => $price_per_liter,
'total_price' => $total_price,
'mileage' => $mileage,
]);
if ($result === true) {

View File

@@ -30,17 +30,21 @@ class VehicleController extends Controller {
}
$vehicle = new Vehicle();
$default_vehicle = $vehicle->getDefaultVehicle($_SESSION['user']['id']);
$is_default = $default_vehicle ? 0 : 1;
$result = $vehicle->create([
'name' => $name,
'registration_plate' => strtoupper($registration_plate),
'fuel_type' => $fuel_type,
'note' => $note,
'user_id' => $_SESSION['user']['id'],
'is_default' => $is_default
]);
if ($result === true) {
$this->redirect('/vehicles');
$this->redirect('/');
} else {
$this->view('vehicles/create', ['title' => 'Create vehicle', 'error' => $result, 'validationErrors' => []] );
}
@@ -56,7 +60,35 @@ class VehicleController extends Controller {
}
public function delete() {
// TODO: Delete vehicle (to be implemented later)
if(!$_SERVER['REQUEST_METHOD'] === 'POST') {
echo "Wrong method";
return;
}
// TODO: Validate the request
$vehicle_id = $_POST['vehicle_id'];
$vehicle = new Vehicle();
$result = $vehicle->delete($vehicle_id, $_SESSION['user']['id']);
if($result != true) {
echo "Something went wrong";
return;
}
header("Location: /vehicles");
}
public function setDefault() {
$vehicle = new Vehicle();
// TODO: Validate the request
$result = $vehicle->setDefaultVehicle($_POST['vehicle_id'], $_SESSION['user']['id']);
if($result != true) {
echo "Something went wrong";
return;
}
header("Location: /");
}
public function api_get() {

View File

@@ -10,12 +10,12 @@ class Refuel {
public function create($data) {
try{
$stmt = $this->db->prepare("
INSERT INTO refueling_records (user_id, vehicle_id, fuel_type, note, liters, price_per_liter, total_price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
INSERT INTO refueling_records (user_id, vehicle_id, fuel_type, note, liters, price_per_liter, total_price, mileage, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
$stmt->bind_param(
"iissddd",
"iissdddi",
$data['user_id'],
$data['vehicle_id'],
$data['fuel_type'],
@@ -23,6 +23,7 @@ class Refuel {
$data['liters'],
$data['price_per_liter'],
$data['total_price'],
$data['mileage'],
);
if ($stmt->execute()) {
@@ -37,15 +38,24 @@ class Refuel {
public function latest_data($vehicle_id, $record_count) {
try {
$stmt = $this->db->prepare("
SELECT `liters`, `price_per_liter`, `total_price`, `created_at`
$sql = "
SELECT `liters`, `price_per_liter`, `total_price`, `mileage`, `created_at`
FROM `refueling_records`
WHERE `vehicle_id` = ?
ORDER BY created_at DESC
LIMIT ?;
");
ORDER BY created_at DESC";
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()) {
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
@@ -59,24 +69,35 @@ class Refuel {
}
}
public function latest_one($user_id, $record_count = 1) {
public function latest_one($vehicle_id, $record_count = 1) {
try {
$stmt = $this->db->prepare("
$sql = "
SELECT
`r`.`vehicle_id`,
`v`.`name` AS `vehicle_name`,
`r`.`liters`,
`r`.`price_per_liter`,
`r`.`total_price`,
`r`.`mileage`,
`r`.`note`,
`r`.`created_at`
FROM `refueling_records` AS `r`
JOIN `vehicles` AS `v` ON `r`.`vehicle_id` = `v`.`id`
WHERE `r`.`user_id` = ?
ORDER BY `r`.`created_at` DESC
LIMIT ?;
");
WHERE `r`.`vehicle_id` = ?
ORDER BY `r`.`created_at` DESC";
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", $user_id, $record_count);
if ($stmt->execute()) {
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);

View File

@@ -29,7 +29,7 @@ class User {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);
$stmt = $this->db->prepare("INSERT INTO users (username, email, password, points, created_at) VALUES (?, ?, ?, 0, NOW())");
$stmt = $this->db->prepare("INSERT INTO users (username, email, password, created_at) VALUES (?, ?, ?, NOW())");
$stmt->bind_param("sss", $username, $email, $hashedPassword);
if ($stmt->execute()) {

View File

@@ -10,17 +10,18 @@ class Vehicle {
public function create($data) {
try{
$stmt = $this->db->prepare("
INSERT INTO vehicles (user_id, name, registration_plate, fuel_type, note, created_at)
VALUES (?, ?, ?, ?, ?, NOW())
INSERT INTO vehicles (user_id, name, registration_plate, fuel_type, note, is_default, created_at)
VALUES (?, ?, ?, ?, ?, ?, NOW())
");
$stmt->bind_param(
"issss",
"issssi",
$data['user_id'],
$data['name'],
$data['registration_plate'],
$data['fuel_type'],
$data['note'],
$data['is_default'],
);
if ($stmt->execute()) {
@@ -34,7 +35,7 @@ class Vehicle {
}
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->execute();
$result = $stmt->get_result();
@@ -60,4 +61,61 @@ class Vehicle {
return $result->fetch_assoc();
}
public function setDefaultVehicle($vehicle_id, $user_id) {
try {
$this->db->begin_transaction();
$stmt = $this->db->prepare("
UPDATE `vehicles`
SET `is_default` = 0
WHERE `user_id` = ? AND `is_default` = 1
");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$stmt->close();
$stmt = $this->db->prepare("
UPDATE `vehicles`
SET `is_default` = 1
WHERE `id` = ? AND `user_id` = ?
");
$stmt->bind_param("ii", $vehicle_id, $user_id);
if ($stmt->execute()) {
$this->db->commit();
return true;
} else {
$this->db->rollback();
return "Error: " . $stmt->error;
}
} catch (mysqli_sql_exception $e) {
$this->db->rollback();
return $e->getMessage();
}
}
public function delete($vehicle_id, $user_id) {
try {
$stmt = $this->db->prepare("SELECT id FROM vehicles WHERE id = ? AND user_id = ?");
$stmt->bind_param("ii", $vehicle_id, $user_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
return "Error: Unauthorized action or vehicle not found.";
}
$stmt = $this->db->prepare("DELETE FROM vehicles WHERE id = ?");
$stmt->bind_param("i", $vehicle_id);
if ($stmt->execute()) {
return true;
} else {
return "Error: " . $stmt->error;
}
} catch (mysqli_sql_exception $e) {
return $e->getMessage();
}
}
}

View File

@@ -5,8 +5,15 @@
<section class="dashboard">
<h1>Welcome, <?= htmlspecialchars($_SESSION['user']['username']) ?>!</h1>
<?php if(!isset($data['default_car'])): ?>
<div id="intro">
<a href="/vehicles/create">Create your first vehicle</a>
</div>
<?php elseif (isset($data['latest_record'])): ?>
<div id="actions">
<a href="/refuel/create" class="btn-green">Add new refuel record!</a>
<a href="/refuel/create" class="btn-green">Add new refuel record</a>
<a href="/vehicles" class="btn-primary">List all vehicles</a>
<a class="btn-warning" id="btn-offline-add">Add new offline refuel record</a>
</div>
@@ -23,6 +30,12 @@
<p><?= $data['latest_record']['price_per_liter'] ?>,-/liter</p>
<b>Total price:</b>
<p><?= $data['latest_record']['total_price'] ?>,-</p>
<b>Mileage:</b>
<p><?= $data['latest_record']['mileage'] ?> km</p>
<?php if (isset($data['latest_record']['note'])): ?>
<b>Note:</b>
<p><?= $data['latest_record']['note'] ?></p>
<?php endif; ?>
</div>
</section>
@@ -45,7 +58,25 @@
<p><?= $data['default_car']['name'] . " | " . $data['default_car']['registration_plate']?></p>
<canvas id="chart-gas-price"></canvas>
</section>
<section class="card history-graph">
<h2>Average fuel consumption</h2>
<hr>
<p><?= $data['default_car']['name'] . " | " . $data['default_car']['registration_plate']?></p>
<b id="avg-fl-cnsmp"></b>
</section>
</div>
<?php else: ?>
<div id="actions">
<a href="/refuel/create" class="btn-green">Add new refuel record</a>
<a href="/vehicles" class="btn-primary">List all vehicles</a>
<a class="btn-warning" id="btn-offline-add">Add new offline refuel record</a>
</div>
<div class="alert-warning">
<p>Default vehicle <b><i><?= $data['default_car']['name'] ?></i></b> doesn't have any refuel record yet.</p>
<p>Select another vehicle or create first refuel record.</p>
</div>
<?php endif; ?>
</section>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
@@ -64,4 +95,29 @@
},
});
</script>
<script>
const data2 = <?= json_encode($data['date_price_data']); ?>;
let cnt_ltr = 0;
let last_ltr = 0;
let last_km = 0;
let first_km = 0;
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]
}
// cnt_ltr -= last_ltr // this would be used if we would track consumption from 0km
last_km -= first_km
document.querySelector("#avg-fl-cnsmp").textContent =
(cnt_ltr/last_km*100).toFixed(1) + " l/100km";
</script>
<script defer src="/js/offline-records.js"></script>

View File

@@ -52,6 +52,12 @@
<small class="error"><?= $this->get('validationErrors')['total_price'] ?></small>
<?php endif; ?>
<label for="mileage">Mileage</label>
<input type="number" name="mileage" id="mileage" min="0" step="1" value="<?= htmlspecialchars($_POST['mileage'] ?? '0') ?>">
<?php if (isset($this->get('validationErrors')['mileage'])): ?>
<small class="error"><?= $this->get('validationErrors')['mileage'] ?></small>
<?php endif; ?>
<label for="note">Note</label>
<input type="text" name="note" id="note" value="<?= htmlspecialchars($_POST['note'] ?? '') ?>">
<?php if (isset($this->get('validationErrors')['note'])): ?>

View File

@@ -4,7 +4,7 @@
<p>No vehicles yet. <a href="/vehicles/create">Add your first vehicle</a>.</p>
<?php else: ?>
<div class="btn-wrapper">
<a href="/vehicles/create" class="btn-green">Add new vehicle!</a>
<a href="/vehicles/create" class="btn-green">Add new vehicle</a>
</div>
<div class="vehicle-wrapper">
<?php foreach ($this->get('vehicles') as $vehicle): ?>
@@ -13,9 +13,21 @@
<p><?= htmlspecialchars($vehicle['registration_plate']) ?></p>
<p><?= htmlspecialchars($vehicle['fuel_type']) ?></p>
<p><?= htmlspecialchars($vehicle['note'] ?? "") ?></p>
<div class="actions">
<a href="/vehicles/edit?id=<?= $vehicle['id'] ?>">Edit</a>
<a href="/vehicles/delete?id=<?= $vehicle['id'] ?>" onclick="return confirm('Are you sure you want to delete this habit?')">Delete</a>
<div class="vehicle-actions">
<br>
<form method="POST" action="/vehicles/delete">
<input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none">
<input type="submit" value="Delete vehicle" class="btn-danger">
</form>
<?php if(!$vehicle['is_default']): ?>
<br>
<form method="POST" action="/vehicles/default">
<input type="number" name="vehicle_id" value="<?= $vehicle['id'] ?>" style="display: none">
<input type="submit" value="Set as default" class="btn-primary">
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>

View File

@@ -63,7 +63,6 @@ class Database {
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
points INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;";
@@ -99,6 +98,7 @@ class Database {
liters DOUBLE(10, 2) NOT NULL,
price_per_liter DOUBLE(10, 2) NOT NULL,
total_price DOUBLE(10, 2) NOT NULL,
mileage INT UNSIGNED NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE

View File

@@ -7,6 +7,11 @@ class View
// Store the 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
ob_start();
require_once views . $view . '.php';

View File

@@ -6,6 +6,10 @@ services:
MARIADB_ROOT_PASSWORD: root
ports:
- 3306:3306
volumes:
- fuelstats_mariadb_data:/var/lib/mysql
networks:
- fuelstats-network
profiles: ["prod", "dev"]
phpmyadmin:
@@ -15,6 +19,11 @@ services:
- 8080:80
environment:
- PMA_ARBITRARY=1
- PMA_HOST=mariadb
depends_on:
- mariadb
networks:
- fuelstats-network
profiles: ["dev"]
fuelstats:
@@ -28,4 +37,13 @@ services:
depends_on:
- mariadb
restart: on-failure:2
networks:
- fuelstats-network
profiles: ["prod"]
networks:
fuelstats-network:
driver: bridge
volumes:
fuelstats_mariadb_data:

View File

@@ -9,7 +9,6 @@
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin-top: 2rem;
}
.card {

View File

@@ -52,3 +52,8 @@ h1 {
background-color: var(--clr-warning-muted);
border: var(--borderWidth-thin) solid var(--clr-border-danger);
}
#actions {
padding-top: 1rem;
padding-bottom: 2rem;
}

View File

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

View File

@@ -46,7 +46,8 @@ $router->group('/vehicles', ['RequireAuth'], function ($router) {
$router->add('', 'VehicleController@index');
$router->add('/create', 'VehicleController@create');
$router->add('/edit/{id}', 'VehicleController@edit');
$router->add('/delete/{id}', 'VehicleController@delete');
$router->add('/delete', 'VehicleController@delete');
$router->add('/default', 'VehicleController@setDefault');
});
$router->group('/refuel', ['RequireAuth'], function ($router) {

View File

@@ -45,11 +45,15 @@ setInterval(async () => {
showDashboard();
});
}
}
if (localStorage.getItem("refuelOfflineData")) {
btnOffline.textContent = "Sync data";
btnOffline.setAttribute("disabled", "disabled");
}
if (localStorage.getItem("refuelOfflineData")) {
Array.from(divActions.children).forEach(
(el) => (el.style.display = "none"),
);
btnOffline.style.display = "block";
btnOffline.textContent = "Sync data";
btnOffline.setAttribute("disabled", "disabled");
}
if (isOnline && !visible) {
@@ -107,7 +111,18 @@ btnOffline.addEventListener("click", async (e) => {
});
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");
@@ -150,11 +165,6 @@ btnOffline.addEventListener("click", async (e) => {
</div>
<section class="form">
<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">
<label for="vehicle">Vehicle</label>
<select name="vehicle" id="vehicle">
@@ -164,13 +174,7 @@ btnOffline.addEventListener("click", async (e) => {
`<option value="${el.id}">${el.name} | ${el.registration_plate}</option>`,
)
.join("")}
<!-- <?php foreach ($this->get('vehicles') as $vehicle): ?> -->
<!-- <option value="<?= $vehicle['id'] ?>"><?= $vehicle['name'] . " | " . $vehicle['registration_plate'] ?></option> -->
<!-- <?php endforeach; ?> -->
</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>
<select name="fuel_type" id="fuel_type">
@@ -182,39 +186,67 @@ btnOffline.addEventListener("click", async (e) => {
<option value="Premium Gasoline 98">Premium Gasoline 98</option>
<option value="Other">Other</option>
</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>
<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> -->
<!-- <?php endif; ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['liters'] ?></small> -->
<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">
<!-- <?php if (isset($this->get('validationErrors')['price_per_liter'])): ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['price_per_liter'] ?></small> -->
<!-- <?php endif; ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['price_per_liter'] ?></small> -->
<label for="total_price">Total price</label>
<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> -->
<!-- <?php endif; ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['total_price'] ?></small> -->
<label for="mileage">Mileage</label>
<input type="number" name="mileage" id="mileage" min="0" step="1" value="0">
<!-- <small class="error"><?= $this->get('validationErrors')['mileage'] ?></small> -->
<label for="note">Note</label>
<input type="text" name="note" id="note">
<!-- <?php if (isset($this->get('validationErrors')['note'])): ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['note'] ?></small> -->
<!-- <?php endif; ?> -->
<!-- <small class="error"><?= $this->get('validationErrors')['note'] ?></small> -->
<input type="submit" id="btn-offline-submit" value="Create fuel record">
</form>
</section>
`;
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");
btnSubmit.addEventListener("click", (e) => {
e.preventDefault();
@@ -229,6 +261,7 @@ btnOffline.addEventListener("click", async (e) => {
total_price: document.querySelector(
"form#offline_refuel_add #total_price",
).value,
mileage: document.querySelector("form#offline_refuel_add #mileage").value,
note: document.querySelector("form#offline_refuel_add #note").value,
};