Compare commits
6 Commits
ea3afa2507
...
fr/intro
Author | SHA1 | Date | |
---|---|---|---|
7517bcb78f | |||
f90c707435 | |||
60423d37ce | |||
5989fba225 | |||
ba11c41147 | |||
64c7fd15a1 |
16
TODO.md
16
TODO.md
@@ -7,15 +7,15 @@
|
||||
|
||||
## 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
|
||||
- [x] Sync offline data from locale storage
|
||||
- [ ] Include kilometer state of an car
|
||||
- [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
|
||||
@@ -25,20 +25,20 @@
|
||||
## What has to be done
|
||||
- [x] Vehicle delete
|
||||
- [ ] intro tutorial when no car exist or just dont show anything
|
||||
- [ ] change/set default car
|
||||
- [ ] hide errors
|
||||
- [x] change/set default car
|
||||
- [x] hide errors
|
||||
|
||||
## Nice to have
|
||||
- [ ] specific car view - charts, fuel records
|
||||
- [ ] remove/edit fuel record
|
||||
- [ ] Include kilometer state of an car
|
||||
- [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
|
||||
- [ ] Fix vehicle deletion - wrong redirect
|
||||
|
||||
- [ ] Update diagrams in README.md
|
||||
|
||||
|
@@ -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'], 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_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',
|
||||
|
@@ -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"]);
|
||||
@@ -57,6 +59,7 @@ class RefuelController extends Controller {
|
||||
'liters' => $liters,
|
||||
'price_per_liter' => $price_per_liter,
|
||||
'total_price' => $total_price,
|
||||
'mileage' => $mileage,
|
||||
]);
|
||||
|
||||
if ($result === true) {
|
||||
|
@@ -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' => []] );
|
||||
}
|
||||
@@ -72,7 +76,19 @@ class VehicleController extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->view('vehicles/index', ['title' => 'Vehicles', 'vehicles' => $vehicles]);
|
||||
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() {
|
||||
|
@@ -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()) {
|
||||
@@ -38,7 +39,7 @@ class Refuel {
|
||||
public function latest_data($vehicle_id, $record_count) {
|
||||
try {
|
||||
$stmt = $this->db->prepare("
|
||||
SELECT `liters`, `price_per_liter`, `total_price`, `created_at`
|
||||
SELECT `liters`, `price_per_liter`, `total_price`, `mileage`, `created_at`
|
||||
FROM `refueling_records`
|
||||
WHERE `vehicle_id` = ?
|
||||
ORDER BY created_at DESC
|
||||
@@ -59,7 +60,7 @@ 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("
|
||||
SELECT
|
||||
@@ -68,15 +69,17 @@ class Refuel {
|
||||
`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` = ?
|
||||
WHERE `r`.`vehicle_id` = ?
|
||||
ORDER BY `r`.`created_at` DESC
|
||||
LIMIT ?;
|
||||
");
|
||||
|
||||
$stmt->bind_param("ii", $user_id, $record_count);
|
||||
$stmt->bind_param("ii", $vehicle_id, $record_count);
|
||||
if ($stmt->execute()) {
|
||||
$result = $stmt->get_result();
|
||||
$data = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
@@ -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()) {
|
||||
|
@@ -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()) {
|
||||
@@ -61,6 +62,39 @@ 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 = ?");
|
||||
|
@@ -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,28 @@
|
||||
},
|
||||
});
|
||||
</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>
|
||||
|
@@ -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'])): ?>
|
||||
|
@@ -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,11 +13,19 @@
|
||||
<p><?= htmlspecialchars($vehicle['registration_plate']) ?></p>
|
||||
<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>
|
||||
<?php endforeach; ?>
|
||||
|
@@ -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
|
||||
|
@@ -6,6 +6,8 @@ services:
|
||||
MARIADB_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306:3306
|
||||
networks:
|
||||
- fuelstats-network
|
||||
profiles: ["prod", "dev"]
|
||||
|
||||
phpmyadmin:
|
||||
@@ -15,6 +17,11 @@ services:
|
||||
- 8080:80
|
||||
environment:
|
||||
- PMA_ARBITRARY=1
|
||||
- PMA_HOST=mariadb
|
||||
depends_on:
|
||||
- mariadb
|
||||
networks:
|
||||
- fuelstats-network
|
||||
profiles: ["dev"]
|
||||
|
||||
fuelstats:
|
||||
@@ -28,4 +35,10 @@ services:
|
||||
depends_on:
|
||||
- mariadb
|
||||
restart: on-failure:2
|
||||
networks:
|
||||
- fuelstats-network
|
||||
profiles: ["prod"]
|
||||
|
||||
networks:
|
||||
fuelstats-network:
|
||||
driver: bridge
|
||||
|
@@ -9,7 +9,6 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ $router->group('/vehicles', ['RequireAuth'], function ($router) {
|
||||
$router->add('/create', 'VehicleController@create');
|
||||
$router->add('/edit/{id}', 'VehicleController@edit');
|
||||
$router->add('/delete', 'VehicleController@delete');
|
||||
$router->add('/default', 'VehicleController@setDefault');
|
||||
});
|
||||
|
||||
$router->group('/refuel', ['RequireAuth'], function ($router) {
|
||||
|
@@ -204,6 +204,12 @@ btnOffline.addEventListener("click", async (e) => {
|
||||
<!-- <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'])): ?> -->
|
||||
@@ -229,6 +235,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,
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user