17 Commits

Author SHA1 Message Date
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
2201430f59 Partially done
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 16s
2025-01-26 22:22:12 +01:00
18c78e37a4 In progress: offline fuel record creation 2025-01-26 17:21:30 +01:00
ccbb0eac64 Added: Stats in dashboard
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-01-05 21:24:55 +01:00
21c2f4598b Added: Create new refuel record
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-01-05 19:05:30 +01:00
860a20d946 Added: Fuel record create - not complete yet
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-01-03 17:10:08 +01:00
c5955010cb Edited: vehicle list styled
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 13s
2025-01-03 16:16:15 +01:00
fc163431f8 Added: Gitea Workflow
All checks were successful
Build and Deploy Zola Website / build_and_deploy (push) Successful in 12s
2025-01-03 00:45:54 +01:00
15029970d6 Fix: wrong action url in vehicle create 2025-01-03 00:18:29 +01:00
e13edeccfc Edited: vehicle create route renamed from vehicle add 2025-01-02 02:02:14 +01:00
28 changed files with 1022 additions and 86 deletions

View File

@@ -0,0 +1,36 @@
name: Build and Deploy Zola Website
on:
push:
branches:
- master
env:
HOST: ${{ secrets.SERVER_IP }}
SSH_USERNAME: ${{ secrets.USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
DEST_FOLDER: "/srv/www/cz/filiprojek/fuelstats"
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Deploy
run: |
apt update -y && apt-get install -y --no-install-recommends rsync
eval "$(ssh-agent -s)"
ssh-add - <<< "${SSH_PRIVATE_KEY}"
mkdir -p ~/.ssh/
ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts
rsync -r --delete-after ./* "${SSH_USERNAME}@${HOST}:${{ env.DEST_FOLDER }}"
- name: Copy environment.php
run: |
eval "$(ssh-agent -s)"
ssh-add - <<< "${SSH_PRIVATE_KEY}"
mkdir -p ~/.ssh/
ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts
ssh ${SSH_USERNAME}@${HOST} "cp /var/websrvenv/environment.php /srv/www/cz/filiprojek/fuelstats/config/environment.php"

BIN
.screenshots/class.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
.screenshots/dlm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
.screenshots/usecase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -53,5 +53,13 @@ php -S localhost:8000 -t ./public
4. Track your fuel consumption and spending through the dashboard. 4. Track your fuel consumption and spending through the dashboard.
5. View detailed stats and graphs to analyze your driving habits. 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 ## License
This project is licensed under GPL3.0 and later. More information is available in the `LICENSE` file. This project is licensed under GPL3.0 and later. More information is available in the `LICENSE` file.

46
TODO.md
View File

@@ -6,13 +6,39 @@
- [ ] edit user data - change password, mail... - [ ] edit user data - change password, mail...
## Core of the app ## Core of the app
- [ ] header and navbar - [ ] intro tutorial when no car exist or just dont show anything
- [ ] dashboard - [x] change/set default car
- [x] css - [ ] specific car view - charts, fuel records
- [ ] its just plain - [ ] remove/edit fuel record
- [ ] graphs
- [x] Habits list ## Until release
- [ ] css - [x] Sync offline data from locale storage
- [ ] Habits create - [x] Include kilometer state of an car
- [ ] validate cron input - [ ] More charts
- [ ] Habits track - [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,10 +3,32 @@ class DashboardController extends Controller {
public function index() { public function index() {
$vehicle = new Vehicle(); $vehicle = new Vehicle();
$vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']); $vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
$default_car = $vehicle->getDefaultVehicle($_SESSION['user']['id']) ?? null;
$refuel = new Refuel();
$data = [
"date" => [],
"price" => [],
"mileage" => [],
"liters" => []
];
$raw_data = $default_car ? $refuel->latest_data($default_car['id'], 5) : [];
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_data = $default_car ? $refuel->latest_one($default_car['id']) : [];
$latest_record = !empty($latest_data) ? $latest_data[0] : null;
$this->view('dashboard/index', [ $this->view('dashboard/index', [
'title' => 'Dashboard', 'title' => 'Dashboard',
'vehicles' => $vehicles, 'vehicles' => $vehicles,
'date_price_data' => $data,
'default_car' => $default_car,
'latest_record' => $latest_record,
]); ]);
} }

View File

@@ -0,0 +1,88 @@
<?php
class RefuelController extends Controller {
public function create() {
if($_SERVER['REQUEST_METHOD'] === 'GET'){
$vehicle = new Vehicle();
$vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
$this->view('refuel/create', [
'title' => "New refuel record",
'vehicles' => $vehicles,
]);
return;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$vehicle_id = $_POST['vehicle'] ?? '';
$fuel_type = $_POST['fuel_type'] ?? '';
$liters = $_POST['liters'] ?? '';
$price_per_liter = $_POST['price_per_liter'] ?? '';
$total_price = $_POST['total_price'] ?? '';
$mileage = $_POST['mileage'] ?? '';
$note = $_POST['note'] ?? '';
$validator = new Validator();
$validator->required('vehicle', $vehicle_id);
$validator->required('fuel_type', $fuel_type);
$validator->required('liters', $liters);
$validator->required('price_per_liter', $price_per_liter);
$validator->required('total_price', $total_price);
$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"]);
}
if($note == "") $note = NULL;
if (!$validator->passes()) {
$vehicle = new Vehicle();
$vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
$this->view('refuel/create', [
'error' => 'Please correct the errors below.',
'validationErrors' => $validator->errors() ?: [],
'vehicles' => $vehicles,
'title' => 'New refuel record',
]);
return;
}
$record = new Refuel();
$result = $record->create([
'user_id' => $_SESSION['user']['id'],
'vehicle_id' => $vehicle_id,
'fuel_type' => $fuel_type,
'note' => $note,
'liters' => $liters,
'price_per_liter' => $price_per_liter,
'total_price' => $total_price,
'mileage' => $mileage,
]);
if ($result === true) {
$this->redirect('/');
} else {
$vehicle = new Vehicle();
$vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
$this->view('refuel/create', [
'title' => 'New refuel record',
'error' => $result,
'validationErrors' => [],
'vehicles' => $vehicles,
]);
}
return;
}
}
public function edit() {
// Edit refuel record (to be implemented later)
}
public function delete() {
// Delete refuel record (to be implemented later)
}
}

View File

@@ -22,7 +22,7 @@ class VehicleController extends Controller {
if($note == "") $note = NULL; if($note == "") $note = NULL;
if (!$validator->passes()) { if (!$validator->passes()) {
$this->view('vehicle/create', [ $this->view('vehicles/create', [
'error' => 'Please correct the errors below.', 'error' => 'Please correct the errors below.',
'validationErrors' => $validator->errors() ?: [], 'validationErrors' => $validator->errors() ?: [],
]); ]);
@@ -30,17 +30,21 @@ class VehicleController extends Controller {
} }
$vehicle = new Vehicle(); $vehicle = new Vehicle();
$default_vehicle = $vehicle->getDefaultVehicle($_SESSION['user']['id']);
$is_default = $default_vehicle ? 0 : 1;
$result = $vehicle->create([ $result = $vehicle->create([
'name' => $name, 'name' => $name,
'registration_plate' => strtoupper($registration_plate), 'registration_plate' => strtoupper($registration_plate),
'fuel_type' => $fuel_type, 'fuel_type' => $fuel_type,
'note' => $note, 'note' => $note,
'user_id' => $_SESSION['user']['id'], 'user_id' => $_SESSION['user']['id'],
'is_default' => $is_default
]); ]);
if ($result === true) { if ($result === true) {
$this->redirect('/vehicles'); $this->redirect('/');
} else { } else {
$this->view('vehicles/create', ['title' => 'Create vehicle', 'error' => $result, 'validationErrors' => []] ); $this->view('vehicles/create', ['title' => 'Create vehicle', 'error' => $result, 'validationErrors' => []] );
} }
@@ -52,10 +56,49 @@ class VehicleController extends Controller {
public function edit() { public function edit() {
// Edit vehicle (to be implemented later) // TODO: Edit vehicle (to be implemented later)
} }
public function delete() { public function delete() {
// 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() {
if(!$_SERVER['REQUEST_METHOD'] === 'GET') {
echo "Wrong method, use GET";
return;
}
$vehicle = new Vehicle();
$result = $vehicle->getVehiclesByUser($_SESSION['user']['id']);
echo json_encode($result);
} }
} }

95
app/models/Refuel.php Normal file
View File

@@ -0,0 +1,95 @@
<?php
class Refuel {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
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, mileage, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
$stmt->bind_param(
"iissdddi",
$data['user_id'],
$data['vehicle_id'],
$data['fuel_type'],
$data['note'],
$data['liters'],
$data['price_per_liter'],
$data['total_price'],
$data['mileage'],
);
if ($stmt->execute()) {
return true;
} else {
return "Error: " . $stmt->error;
}
} catch(mysqli_sql_exception $e) {
return $e->getMessage();
}
}
public function latest_data($vehicle_id, $record_count) {
try {
$stmt = $this->db->prepare("
SELECT `liters`, `price_per_liter`, `total_price`, `mileage`, `created_at`
FROM `refueling_records`
WHERE `vehicle_id` = ?
ORDER BY created_at DESC
LIMIT ?;
");
$stmt->bind_param("ii", $vehicle_id, $record_count);
if ($stmt->execute()) {
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return array_reverse($data);
} else {
return "Error: " . $stmt->error;
}
} catch (mysqli_sql_exception $e) {
return $e->getMessage();
}
}
public function latest_one($vehicle_id, $record_count = 1) {
try {
$stmt = $this->db->prepare("
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`.`vehicle_id` = ?
ORDER BY `r`.`created_at` DESC
LIMIT ?;
");
$stmt->bind_param("ii", $vehicle_id, $record_count);
if ($stmt->execute()) {
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return array_reverse($data);
} else {
return "Error: " . $stmt->error;
}
} catch (mysqli_sql_exception $e) {
return $e->getMessage();
}
}
}

View File

@@ -29,7 +29,7 @@ class User {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT); $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); $stmt->bind_param("sss", $username, $email, $hashedPassword);
if ($stmt->execute()) { if ($stmt->execute()) {

View File

@@ -10,17 +10,18 @@ class Vehicle {
public function create($data) { public function create($data) {
try{ try{
$stmt = $this->db->prepare(" $stmt = $this->db->prepare("
INSERT INTO vehicles (user_id, name, registration_plate, fuel_type, note, created_at) INSERT INTO vehicles (user_id, name, registration_plate, fuel_type, note, is_default, created_at)
VALUES (?, ?, ?, ?, ?, NOW()) VALUES (?, ?, ?, ?, ?, ?, NOW())
"); ");
$stmt->bind_param( $stmt->bind_param(
"issss", "issssi",
$data['user_id'], $data['user_id'],
$data['name'], $data['name'],
$data['registration_plate'], $data['registration_plate'],
$data['fuel_type'], $data['fuel_type'],
$data['note'], $data['note'],
$data['is_default'],
); );
if ($stmt->execute()) { if ($stmt->execute()) {
@@ -33,9 +34,9 @@ class Vehicle {
} }
} }
public function getVehiclesByUser($userId) { 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, created_at FROM vehicles WHERE user_id = ?");
$stmt->bind_param("i", $userId); $stmt->bind_param("i", $user_id);
$stmt->execute(); $stmt->execute();
$result = $stmt->get_result(); $result = $stmt->get_result();
@@ -46,4 +47,75 @@ class Vehicle {
return $vehicles; return $vehicles;
} }
public function getDefaultVehicle($user_id) {
$stmt = $this->db->prepare("
SELECT id, name, registration_plate, fuel_type, note, is_default
FROM vehicles
WHERE user_id = ? AND is_default = TRUE
LIMIT 1
");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$result = $stmt->get_result();
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

@@ -1,47 +1,122 @@
<link rel="stylesheet" href="/css/dashboard.css"> <link rel="stylesheet" href="/css/dashboard.css">
<link rel="stylesheet" href="/css/form.css">
<link rel="stylesheet" href="/css/vehicle_create.css">
<section class="dashboard"> <section class="dashboard">
<h1>Welcome, <?= htmlspecialchars($_SESSION['user']['username']) ?>!</h1> <h1>Welcome, <?= htmlspecialchars($_SESSION['user']['username']) ?>!</h1>
<div> <?php if(!isset($data['default_car'])): ?>
<a href="/refuel/add" class="btn-green">Add new refuel record!</a>
<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="/vehicles" class="btn-primary">List all vehicles</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>
<div class="card-wrapper"> <div class="card-wrapper">
<section class="card upcoming"> <section class="card latest">
<h2>Upcoming</h2> <h2>Latest fuel record</h2>
<div class="habit"> <hr>
<b>Habit Title</b> <div>
<p>Frequency</p> <b>Car:</b>
<p>Reward points</p> <p><?= $data['latest_record']['vehicle_name'] ?></p>
<b>Liters:</b>
<p><?= $data['latest_record']['liters'] ?> liters</p>
<b>Price per liter:</b>
<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> </div>
</section> </section>
<section class="card recent"> <section class="card">
<h2>Recent</h2> <h2>Default car</h2>
<div class="habit"> <hr>
<b>Habit Title</b> <b>Car</b>
<p>Frequency</p> <p><?= $data['default_car']['name'] ?></p>
<p>Reward points</p> <b>Registration plate</b>
</div> <p><?= $data['default_car']['registration_plate'] ?></p>
</section> <b>Fuel type</b>
<p><?= $data['default_car']['fuel_type'] ?></p>
<section class="card missed"> <b>Note</b>
<h2>Missed</h2> <p><?= $data['default_car']['note'] ?></p>
<div class="habit">
<b>Habit Title</b>
<p>Frequency</p>
<p>Reward points</p>
</div>
</section> </section>
<section class="card history-graph"> <section class="card history-graph">
<h2>Graph of History</h2> <h2>Chart of Gas price</h2>
<hr>
<p><?= $data['default_car']['name'] . " | " . $data['default_car']['registration_plate']?></p>
<canvas id="chart-gas-price"></canvas>
</section> </section>
<section class="card streak"> <section class="card history-graph">
<h2>Streak</h2> <h2>Average fuel consumption</h2>
<p>You're current streak is 123 days</p> <hr>
<p>Good job!</p> <p><?= $data['default_car']['name'] . " | " . $data['default_car']['registration_plate']?></p>
<b id="avg-fl-cnsmp"></b>
</section> </section>
</div> </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> </section>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('chart-gas-price');
const data = <?= json_encode($data['date_price_data']); ?>;
new Chart(ctx, {
type: 'line',
data: {
labels: [...data['date']],
datasets: [{
label: 'Gas price history',
data: data['price'],
borderWidth: 1,
}]
},
});
</script>
<script>
const data2 = <?= json_encode($data['date_price_data']); ?>;
let cnt_ltr = 0
let cnt_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]
}
cnt_ltr += data2['liters'][i]
cnt_km =+ data2['mileage'][i]
}
console.log("Liters", cnt_ltr, cnt_km, first_km)
console.log("Avg", (cnt_km - first_km) / cnt_ltr)
document.querySelector("#avg-fl-cnsmp").textContent = Math.floor((cnt_km - first_km) / cnt_ltr) + " l/100km"
</script>
<script defer src="/js/offline-records.js"></script>

View File

@@ -6,7 +6,7 @@
<meta name="author" content="Filip Rojek | http://filiprojek.cz"> <meta name="author" content="Filip Rojek | http://filiprojek.cz">
<meta name="email" content="webmaster(@)fofrweb.com"> <meta name="email" content="webmaster(@)fofrweb.com">
<meta name="copyright" content="(c) filiprojek.cz"> <meta name="copyright" content="(c) filiprojek.cz">
<title>Fuel Stats | <?= $data['title'] ?></title> <title>Fuel Stats<?= isset($data['title']) ? " | " . $data['title'] : "" ?></title>
<link rel="stylesheet" href="/css/main.css"> <link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/global.css"> <link rel="stylesheet" href="/css/global.css">
<link rel="stylesheet" href="/css/vars.css"> <link rel="stylesheet" href="/css/vars.css">
@@ -17,7 +17,7 @@
<header> <header>
<div id="hd-left"> <div id="hd-left">
<a href="/"><img src="/img/logo.jpg" alt="home"></a> <a href="/"><img src="/img/logo.jpg" alt="home"></a>
<label><?= $data['title'] ?></label> <label><?= isset($data['title']) ? $data['title'] : "" ?></label>
</div> </div>
<div id="hd-right"> <div id="hd-right">
<?php if (!isset($_SESSION['user'])): ?> <?php if (!isset($_SESSION['user'])): ?>

View File

@@ -6,7 +6,7 @@
<meta name="author" content="Filip Rojek | http://filiprojek.cz"> <meta name="author" content="Filip Rojek | http://filiprojek.cz">
<meta name="email" content="webmaster(@)fofrweb.com"> <meta name="email" content="webmaster(@)fofrweb.com">
<meta name="copyright" content="(c) filiprojek.cz"> <meta name="copyright" content="(c) filiprojek.cz">
<title>Fuel Stats | <?= $data['title'] ?></title> <title>Fuel Stats<?= isset($data['title']) ? " | " . $data['title'] : "" ?></title>
<link rel="stylesheet" href="/css/main.css"> <link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/global.css"> <link rel="stylesheet" href="/css/global.css">
<link rel="stylesheet" href="/css/vars.css"> <link rel="stylesheet" href="/css/vars.css">

125
app/views/refuel/create.php Normal file
View File

@@ -0,0 +1,125 @@
<link rel="stylesheet" href="/css/form.css">
<link rel="stylesheet" href="/css/vehicle_create.css">
<section class="form">
<h1 class="header-form"><?= $this->get('title') ?></h1>
<?php if ($this->get('error')): ?>
<div class="error" style="color: red; margin-bottom: 1rem;">
<?= htmlspecialchars($this->get('error')) ?>
</div>
<?php endif; ?>
<form method="POST" action="/refuel/create">
<label for="vehicle">Vehicle</label>
<select name="vehicle" id="vehicle">
<?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">
<option value="Diesel">Diesel</option>
<option value="Gasoline 95">Gasoline 95</option>
<option value="Gasoline 98">Gasoline 98</option>
<option value="Premium Diesel">Premium Diesel</option>
<option value="Premium Gasoline 95">Premium Gasoline 95</option>
<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="<?= htmlspecialchars($_POST['liters'] ?? '0.0') ?>">
<?php if (isset($this->get('validationErrors')['liters'])): ?>
<small class="error"><?= $this->get('validationErrors')['liters'] ?></small>
<?php endif; ?>
<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="<?= htmlspecialchars($_POST['price_per_liter'] ?? '0.0') ?>">
<?php if (isset($this->get('validationErrors')['price_per_liter'])): ?>
<small class="error"><?= $this->get('validationErrors')['price_per_liter'] ?></small>
<?php endif; ?>
<label for="total_price">Total price</label>
<input type="number" name="total_price" id="total_price" min="0" step=".01" value="<?= htmlspecialchars($_POST['total_price'] ?? '0.0') ?>">
<?php if (isset($this->get('validationErrors')['total_price'])): ?>
<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'])): ?>
<small class="error"><?= $this->get('validationErrors')['note'] ?></small>
<?php endif; ?>
<input type="submit" value="Create fuel record">
</form>
</section>
<script>
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 vehicles = <?= json_encode($data['vehicles']); ?>;
const fuel_sel = document.querySelector("#fuel_type")
const vehic_sel = document.querySelector("#vehicle")
function selectFuel() {
const veh_id = vehic_sel.value
vehicles.forEach(el => {
if(el.id == veh_id) {
fuel_sel.value = el.fuel_type
return
}
})
}
selectFuel()
vehic_sel.addEventListener("change", () => {
selectFuel()
})
</script>

View File

@@ -9,7 +9,7 @@
</div> </div>
<?php endif; ?> <?php endif; ?>
<form method="POST" action="/vehicles/add"> <form method="POST" action="/vehicles/create">
<label for="name">Vehicle name</label> <label for="name">Vehicle name</label>
<input type="text" name="name" id="name" required value="<?= htmlspecialchars($_POST['name'] ?? '') ?>"> <input type="text" name="name" id="name" required value="<?= htmlspecialchars($_POST['name'] ?? '') ?>">

View File

@@ -1,19 +1,34 @@
<link rel="stylesheet" href="/css/vehicles.css"> <link rel="stylesheet" href="/css/vehicles.css">
<section class="vehicles"> <section class="vehicles">
<?php if (empty($this->get('vehicles'))): ?> <?php if (empty($this->get('vehicles'))): ?>
<p>No vehicles yet. <a href="/vehicles/add">Add your first vehicle</a>.</p> <p>No vehicles yet. <a href="/vehicles/create">Add your first vehicle</a>.</p>
<?php else: ?> <?php else: ?>
<div class="btn-wrapper">
<a href="/vehicles/create" class="btn-green">Add new vehicle</a>
</div>
<div class="vehicle-wrapper"> <div class="vehicle-wrapper">
<?php foreach ($this->get('vehicles') as $vehicle): ?> <?php foreach ($this->get('vehicles') as $vehicle): ?>
<div class="vehicle bordered"> <div class="vehicle bordered">
<b><?= htmlspecialchars($vehicle['name']) ?></b> <b><?= htmlspecialchars($vehicle['name']) ?></b>
<a href="/vehicles/edit?id=<?= $vehicle['id'] ?>">Edit</a> | <p><?= htmlspecialchars($vehicle['registration_plate']) ?></p>
<a href="/vehicles/delete?id=<?= $vehicle['id'] ?>" onclick="return confirm('Are you sure you want to delete this habit?')">Delete</a> <p><?= htmlspecialchars($vehicle['fuel_type']) ?></p>
<p><?= htmlspecialchars($vehicle['note'] ?? "") ?></p>
<div class="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>
<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>
</div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<br>
<a href="/vehicles/create" class="btn-green">Add new vehicle!</a>
<?php endif; ?> <?php endif; ?>
</section> </section>

View File

@@ -63,7 +63,6 @@ class Database {
username VARCHAR(50) NOT NULL, username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL,
points INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;"; ) ENGINE=InnoDB;";
@@ -79,6 +78,7 @@ class Database {
registration_plate VARCHAR(50) NOT NULL UNIQUE, registration_plate VARCHAR(50) NOT NULL UNIQUE,
fuel_type ENUM('Diesel', 'Gasoline 95', 'Gasoline 98', 'Premium Diesel', 'Premium Gasoline 95', 'Premium Gasoline 98', 'Other') NOT NULL, fuel_type ENUM('Diesel', 'Gasoline 95', 'Gasoline 98', 'Premium Diesel', 'Premium Gasoline 95', 'Premium Gasoline 98', 'Other') NOT NULL,
note VARCHAR(150) NULL, note VARCHAR(150) NULL,
is_default BOOLEAN DEFAULT FALSE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB; ) ENGINE=InnoDB;
@@ -91,13 +91,17 @@ class Database {
$refuelingTableQuery = " $refuelingTableQuery = "
CREATE TABLE IF NOT EXISTS refueling_records ( CREATE TABLE IF NOT EXISTS refueling_records (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
vehicle_id INT NOT NULL, vehicle_id INT NOT NULL,
fuel_type ENUM('Diesel', 'Gasoline 95', 'Gasoline 98', 'Premium Diesel', 'Premium Gasoline 95', 'Premium Gasoline 98', 'Other') NOT NULL, fuel_type ENUM('Diesel', 'Gasoline 95', 'Gasoline 98', 'Premium Diesel', 'Premium Gasoline 95', 'Premium Gasoline 98', 'Other') NOT NULL,
liters DECIMAL(10, 2) NOT NULL, note VARCHAR(150) NULL,
price_per_liter DECIMAL(10, 2) NOT NULL, liters DOUBLE(10, 2) NOT NULL,
total_price DECIMAL(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, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB; ) ENGINE=InnoDB;
"; ";

View File

@@ -12,6 +12,15 @@ class Validator {
} }
} }
/**
* Check if a field contains numbers
*/
public function number($field, $value, $message = null) {
if(!is_numeric($value)) {
$this->errors[$field] = $message ?? "$field must be an number";
}
}
/** /**
* Check if a field meets minimum length * Check if a field meets minimum length
*/ */
@@ -46,6 +55,10 @@ class Validator {
return $this->errors; return $this->errors;
} }
public function setErrors($errors) {
$this->errors = $errors;
}
/** /**
* Check if validation passed * Check if validation passed
*/ */

View File

@@ -6,6 +6,8 @@ services:
MARIADB_ROOT_PASSWORD: root MARIADB_ROOT_PASSWORD: root
ports: ports:
- 3306:3306 - 3306:3306
networks:
- fuelstats-network
profiles: ["prod", "dev"] profiles: ["prod", "dev"]
phpmyadmin: phpmyadmin:
@@ -15,6 +17,11 @@ services:
- 8080:80 - 8080:80
environment: environment:
- PMA_ARBITRARY=1 - PMA_ARBITRARY=1
- PMA_HOST=mariadb
depends_on:
- mariadb
networks:
- fuelstats-network
profiles: ["dev"] profiles: ["dev"]
fuelstats: fuelstats:
@@ -28,4 +35,10 @@ services:
depends_on: depends_on:
- mariadb - mariadb
restart: on-failure:2 restart: on-failure:2
networks:
- fuelstats-network
profiles: ["prod"] profiles: ["prod"]
networks:
fuelstats-network:
driver: bridge

View File

@@ -9,13 +9,30 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
justify-content: center; justify-content: center;
margin-top: 2rem;
} }
.card { .card {
background-color: var(--clr-secondary); background-color: var(--clr-secondary);
border-radius: var(--border-radious); border-radius: var(--border-radious);
border: var(--borderWidth-thin) solid var(--clr-border); border: var(--borderWidth-thin) solid var(--clr-border);
min-width: 17rem; width: 18rem;
padding: 1rem; padding: 1rem;
} }
#btn-offline-add {
display: none;
}
.alert-warning {
background-color: var(--clr-warning-muted);
border: var(--borderWidth-thin) solid var(--clr-border-danger);
padding: 1rem;
border-radius: var(--border-radious);
}
.alert-danger {
background-color: var(--clr-danger-muted);
border: var(--borderWidth-thin) solid var(--clr-border-danger);
padding: 1rem;
border-radius: var(--border-radious);
}

View File

@@ -20,13 +20,15 @@ h1 {
.btn-secondary, .btn-secondary,
.btn-tertiary, .btn-tertiary,
.btn-green, .btn-green,
.btn-danger { .btn-danger,
.btn-warning {
background-color: var(--clr-primary); background-color: var(--clr-primary);
padding: .5rem; padding: .5rem;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
border-radius: var(--border-radious); border-radius: var(--border-radious);
border: var(--borderWidth-thin) solid var(--clr-border); border: var(--borderWidth-thin) solid var(--clr-border);
color: white;
} }
.btn-secondary { .btn-secondary {
@@ -45,3 +47,13 @@ h1 {
background-color: var(--clr-danger-muted); background-color: var(--clr-danger-muted);
border: var(--borderWidth-thin) solid var(--clr-border-danger); border: var(--borderWidth-thin) solid var(--clr-border-danger);
} }
.btn-warning {
background-color: var(--clr-warning-muted);
border: var(--borderWidth-thin) solid var(--clr-border-danger);
}
#actions {
padding-top: 1rem;
padding-bottom: 2rem;
}

View File

@@ -1,15 +0,0 @@
.habits-wrapper {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.habits .bordered {
border-radius: var(--border-radious);
border: var(--borderWidth-thin) solid var(--clr-border);
width: 17rem;
padding: 1rem;
margin-top: 1rem;
text-align: center;
}

View File

@@ -5,6 +5,7 @@
--clr-tertiary: #151b23; --clr-tertiary: #151b23;
--clr-green: #238636; --clr-green: #238636;
--clr-danger-muted: #f851491a; --clr-danger-muted: #f851491a;
--clr-warning-muted: #e08e455e;
--clr-link-blue: #4493f8; --clr-link-blue: #4493f8;
--clr-light-blue: #39a2ae; --clr-light-blue: #39a2ae;

29
public/css/vehicles.css Normal file
View File

@@ -0,0 +1,29 @@
.vehicle-wrapper {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.vehicle {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--clr-secondary);
border-radius: var(--border-radious);
border: var(--borderWidth-thin) solid var(--clr-border);
width: 17rem;
padding: 1rem;
margin-top: 1rem;
text-align: center;
min-height: 8rem;
}
.btn-wrapper {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
}

View File

@@ -21,7 +21,7 @@ require_once '../core/middlewares/RequireAuth.php';
require_once models . 'User.php'; require_once models . 'User.php';
require_once models . 'Vehicle.php'; require_once models . 'Vehicle.php';
#require_once models . 'Refueling.php'; require_once models . 'Refuel.php';
// Initialize router // Initialize router
$router = new Router(); $router = new Router();
@@ -41,12 +41,22 @@ $router->group('/auth', [], function ($router) {
// dashboard route // dashboard route
$router->add('/dashboard', 'DashboardController@index', ['RequireAuth']); $router->add('/dashboard', 'DashboardController@index', ['RequireAuth']);
// habits routes // vehicle routes
$router->group('/vehicles', ['RequireAuth'], function ($router) { $router->group('/vehicles', ['RequireAuth'], function ($router) {
$router->add('', 'VehicleController@index'); $router->add('', 'VehicleController@index');
$router->add('/add', 'VehicleController@create'); $router->add('/create', 'VehicleController@create');
$router->add('/edit/{id}', 'VehicleController@edit'); $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) {
$router->add('/create', 'RefuelController@create');
});
// API
$router->group('/api/v1', ['RequireAuth'], function ($router) {
$router->add('/vehicles/get', 'VehicleController@api_get');
}); });
$router->dispatch(); $router->dispatch();

View File

@@ -0,0 +1,247 @@
async function checkOnline() {
if (!navigator.onLine) {
console.log("Offline (no network connection)");
return false;
}
try {
const response = await fetch(
"https://www.google.com/favicon.ico?" + new Date().getTime(),
{
mode: "no-cors",
},
);
return true;
} catch (error) {
console.log("Connected to network but no internet access");
return false;
}
}
function showDashboard() {
const offline = document.querySelector(".offline");
offline.remove();
document.querySelector(".dashboard").style.display = "flex";
}
const btnOffline = document.querySelector("#btn-offline-add");
const divActions = document.querySelector("#actions");
let visible = true;
setInterval(async () => {
const isOnline = await checkOnline();
//const isOnline = false; // REMOVE!!!
if (!isOnline) {
if (visible) {
console.log("OFFLINE!!!");
Array.from(divActions.children).forEach(
(el) => (el.style.display = "none"),
);
visible = false;
btnOffline.style.display = "block";
document.querySelector(".hd-left").addEventListener("click", () => {
showDashboard();
});
}
if (localStorage.getItem("refuelOfflineData")) {
btnOffline.textContent = "Sync data";
btnOffline.setAttribute("disabled", "disabled");
}
}
if (isOnline && !visible) {
console.log("BACK ONLINE!!!");
visible = true;
btnOffline.removeAttribute("disabled", "disabled");
// TODO: show buttons back, add sync button instead of record creation
// If user is in a process of adding new offline refuel record, let him finish
// Clear the local storage on each login
}
//}, 5000);
}, 1000);
window.onload = async () => {
const rawData = await fetch("/api/v1/vehicles/get", {
method: "GET",
credentials: "include",
});
const data = await rawData.json();
console.log("Fetched:", data);
localStorage.setItem("vehicles", JSON.stringify(data));
console.log(JSON.parse(localStorage.getItem("vehicles")));
};
btnOffline.addEventListener("click", async (e) => {
e.preventDefault();
if (btnOffline.textContent == "Sync data") {
if (!visible) {
alert("You're still offline. Try again later");
return;
}
try {
let data = localStorage.getItem("refuelOfflineData");
if (!data) {
console.error("No offline data found");
alert("No offline data found");
return;
}
data = JSON.parse(data);
const formData = new FormData();
for (const key in data) {
if (data.hasOwnProperty(key)) {
formData.append(key, data[key]);
}
}
const res = await fetch("/refuel/create", {
method: "POST",
body: formData,
credentials: "include",
});
if (!res.ok) {
throw new Error(`Server error: ${res.statusText}`);
}
localStorage.removeItem("refuelOfflineData");
location.reload();
} catch (err) {
console.error(err);
alert("Something went wrong");
location.reload();
}
return;
}
document.querySelector("section.dashboard").style.display = "none";
try {
vehicles = localStorage.getItem("vehicles");
if (vehicles === null) throw new Error("No data was saved locally");
vehicles = JSON.parse(vehicles);
} catch (err) {
console.error(err);
const offline = document.createElement("div");
offline.classList.add("offline");
offline.innerHTML = `
<div class="alert-danger">
<b>You're Offline</b>
<p>No data was saved locally, please try again later</p>
</div>
`;
document.querySelector("main").appendChild(offline);
// TODO: Add button to navigate back to offline dashboard
return;
}
const offline = document.createElement("div");
offline.classList.add("offline");
offline.innerHTML = `
<div class="alert-warning">
<b>You're Offline</b>
<p>You can create an fuel record locally on your device and sync it later</p>
</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">
${vehicles
.map(
(el) =>
`<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">
<option value="Diesel">Diesel</option>
<option value="Gasoline 95">Gasoline 95</option>
<option value="Gasoline 98">Gasoline 98</option>
<option value="Premium Diesel">Premium Diesel</option>
<option value="Premium Gasoline 95">Premium Gasoline 95</option>
<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; ?> -->
<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; ?> -->
<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; ?> -->
<label for="mileage">Mileage</label>
<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> -->
<!-- <?php endif; ?> -->
<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; ?> -->
<input type="submit" id="btn-offline-submit" value="Create fuel record">
</form>
</section>
`;
document.querySelector("main").appendChild(offline);
const btnSubmit = document.querySelector("#btn-offline-submit");
btnSubmit.addEventListener("click", (e) => {
e.preventDefault();
const formData = {
vehicle: document.querySelector("form#offline_refuel_add #vehicle").value,
fuel_type: document.querySelector("form#offline_refuel_add #fuel_type")
.value,
liters: document.querySelector("form#offline_refuel_add #liters").value,
price_per_liter: document.querySelector(
"form#offline_refuel_add #price_per_liter",
).value,
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,
};
console.log("formData", formData);
localStorage.setItem("refuelOfflineData", JSON.stringify(formData));
alert("Data was locally saved. Sync it later!");
showDashboard();
});
});