Compare commits
	
		
			30 Commits
		
	
	
		
			4c44dac115
			...
			fr/offline
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 18c78e37a4 | |||
| ccbb0eac64 | |||
| 21c2f4598b | |||
| 860a20d946 | |||
| c5955010cb | |||
| fc163431f8 | |||
| 15029970d6 | |||
| e13edeccfc | |||
| be6b465684 | |||
| aded859a79 | |||
| c29bd7cbab | |||
| eff5be49c4 | |||
| d13c490efb | |||
| d98c208df9 | |||
| 85af85b1ee | |||
| 1ff7fc454f | |||
| e0dfc7120f | |||
| 9c90710bf3 | |||
| 4b8ee90d8a | |||
| 59246decd7 | |||
| 147e3b1499 | |||
| daec4ec1c9 | |||
| 3c6ecfb5e2 | |||
| b4e08f28ca | |||
| c4366edb29 | |||
| d9f632da26 | |||
| 2847231376 | |||
| d33d233f0f | |||
| 43960ddcb9 | |||
| 85209ff134 | 
							
								
								
									
										53
									
								
								.docker/nginx.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								.docker/nginx.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| # User and worker process settings | ||||
| user www-data; | ||||
| worker_processes auto; | ||||
| pid /run/nginx.pid; | ||||
|  | ||||
| events { | ||||
|     worker_connections 1024; | ||||
| } | ||||
|  | ||||
| http { | ||||
|     # General settings | ||||
|     sendfile on; | ||||
|     tcp_nopush on; | ||||
|     tcp_nodelay on; | ||||
|     keepalive_timeout 65; | ||||
|     types_hash_max_size 2048; | ||||
|  | ||||
|     include /etc/nginx/mime.types; | ||||
|     default_type application/octet-stream; | ||||
|  | ||||
|     # Logging | ||||
|     access_log /var/log/nginx/access.log; | ||||
|     error_log /var/log/nginx/error.log; | ||||
|  | ||||
|     # Gzip compression | ||||
|     gzip on; | ||||
|     gzip_disable "msie6"; | ||||
|  | ||||
|     # Server configuration | ||||
|     server { | ||||
|         listen 80; | ||||
|         server_name localhost; | ||||
|  | ||||
|         root /var/www/html/public; | ||||
|         index index.php index.html; | ||||
|  | ||||
|         location / { | ||||
|             try_files $uri $uri/ /index.php?$query_string; | ||||
|         } | ||||
|  | ||||
|         location ~ \\.php$ { | ||||
|             include fastcgi_params; | ||||
|             fastcgi_pass 127.0.0.1:9000; | ||||
|             fastcgi_index index.php; | ||||
|             fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | ||||
|         } | ||||
|  | ||||
|         location ~ /\.ht { | ||||
|             deny all; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										22
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| .git | ||||
| .gitignore | ||||
|  | ||||
| storage/logs/* | ||||
| !storage/logs/.gitignore | ||||
|  | ||||
| Dockerfile | ||||
| docker-compose.yaml | ||||
|  | ||||
| *.swp | ||||
| *.swo | ||||
| *.idea/ | ||||
| *.vscode/ | ||||
| *.DS_Store | ||||
|  | ||||
| # Exclude sensitive config files (uncomment if you don't want to include environment config) | ||||
| # config/environment.php | ||||
|  | ||||
| README.md | ||||
| TODO.md | ||||
| LICENSE | ||||
|  | ||||
							
								
								
									
										36
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal 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" | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| *.swp | ||||
							
								
								
									
										18
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| FROM php:8.2-fpm-alpine | ||||
|  | ||||
| WORKDIR /var/www/html | ||||
|  | ||||
| RUN apk add --no-cache nginx curl \ | ||||
|   && docker-php-ext-install mysqli | ||||
|  | ||||
| COPY . /var/www/html | ||||
|  | ||||
| RUN chown -R www-data:www-data /var/www/html \ | ||||
|   && chmod -R 755 /var/www/html | ||||
|  | ||||
| COPY .docker/nginx.conf /etc/nginx/nginx.conf | ||||
|  | ||||
| EXPOSE 80 | ||||
|  | ||||
| CMD php-fpm & nginx -g "daemon off;" | ||||
|  | ||||
							
								
								
									
										6
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -208,8 +208,8 @@ If you develop a new program, and you want it to be of the greatest possible use | ||||
|  | ||||
| To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. | ||||
|  | ||||
|      habit-tracker | ||||
|      Copyright (C) 2024  fr | ||||
|      Fuel Stats | ||||
|      Copyright (C) 2024  Filip Rojek | ||||
|  | ||||
|      This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | ||||
|  | ||||
| @@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
| If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: | ||||
|  | ||||
|      habit-tracker  Copyright (C) 2024  fr | ||||
|      Fuel Stats  Copyright (C) 2024  Filip Rojek | ||||
|      This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|      This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. | ||||
|  | ||||
|   | ||||
							
								
								
									
										54
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,20 +1,32 @@ | ||||
| # Habit Tracker | ||||
| # Fuel Stats | ||||
|  | ||||
| An app for tracking habits and motivation to achieve personal goals | ||||
| An app for tracking your fuel consumption and optimizing your driving efficiency. | ||||
|  | ||||
| ## Used technologies | ||||
| ## Used Technologies | ||||
| - **Frontend:** HTML, CSS, JavaScript | ||||
| - **Backend:** PHP (OOP) | ||||
| - **Database:** MariaDB | ||||
|  | ||||
| ## How to build | ||||
| 1. Clone the repo | ||||
| ## How to Build | ||||
|  | ||||
| ### Build Using Docker | ||||
| Run the container using docker-compose: | ||||
| ```bash | ||||
|    git clone https://git.filiprojek.cz/fr/habit-tracker.git | ||||
| docker-compose --profile <dev|prod> up -d | ||||
| ``` | ||||
|  | ||||
| 2. Create `config/environment.php` | ||||
| - It should have following structure: | ||||
| The app should be available at http://localhost:8000. | ||||
|  | ||||
| PhpMyAdmin should be available at http://localhost:8080. | ||||
|  | ||||
| ### Build Manually | ||||
| 1. Clone the repository: | ||||
| ```bash | ||||
|    git clone https://git.filiprojek.cz/fr/fuel-stats.git | ||||
| ``` | ||||
|  | ||||
| 2. Create `config/environment.php`: | ||||
| - It should have the following structure: | ||||
| ```php | ||||
| <?php | ||||
|  | ||||
| @@ -23,21 +35,23 @@ define('DB_USER', 'your db username'); | ||||
| define('DB_PASS', 'your db password'); | ||||
| define('DB_NAME', 'your db name');  | ||||
| ``` | ||||
| - For the database, you can use included `docker-compose.yaml` which have both MariaDB and PhpMyAdmin | ||||
| - For the database, you can use the included `docker-compose.yaml` which includes both MariaDB and PhpMyAdmin. | ||||
|  | ||||
| 3. Start an local web server | ||||
| - You can use php's integrated server by running this: | ||||
| 3. Start a local web server: | ||||
| - You can use PHP's integrated server by running this: | ||||
| ```bash | ||||
| php -S localhost:8000 | ||||
| php -S localhost:8000 -t ./public | ||||
| ``` | ||||
| - You can use any host and any port you want. | ||||
| - You can use any host and any port you prefer. | ||||
|  | ||||
| ## Usage | ||||
| 1. Register and Login to the app. | ||||
| 2. Add your habits. | ||||
| 3. Mark your habits when you're done doing them. | ||||
| 4. Earn point and unlock achievements by completing you're habits! | ||||
|  | ||||
| ## Licence | ||||
| This project is licensed under GPL3.0 and later. More information is availabe in `LICENSE` file. | ||||
| 1. Register and log in to the app. | ||||
| 2. Add your vehicles with their details (fuel type, registration, etc.). | ||||
| 3. Record each refueling: | ||||
|    - Select your vehicle. | ||||
|    - Input the number of liters, price per liter, and total cost. | ||||
| 4. Track your fuel consumption and spending through the dashboard. | ||||
| 5. View detailed stats and graphs to analyze your driving habits. | ||||
|  | ||||
| ## License | ||||
| This project is licensed under GPL3.0 and later. More information is available in the `LICENSE` file. | ||||
|   | ||||
							
								
								
									
										8
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								TODO.md
									
									
									
									
									
								
							| @@ -6,4 +6,10 @@ | ||||
| - [ ] edit user data - change password, mail... | ||||
|  | ||||
| ## Core of the app | ||||
| - [ ] think about it lol | ||||
| - [ ] intro tutorial when no car exist or just dont show anything | ||||
| - [ ] change/set default car | ||||
| - [ ] specific car view - charts, fuel records | ||||
| - [ ] remove/edit fuel record | ||||
|  | ||||
| - [ ] IndexDB | ||||
|   - [ ]  | ||||
|   | ||||
| @@ -25,10 +25,10 @@ class AuthController extends Controller  { | ||||
|             if($result === true) { | ||||
|                 $this->redirect('/dashboard'); | ||||
|             } else { | ||||
|                 $this->view('auth/signin', ['error' => $result]); | ||||
|                 $this->view('auth/signin', ['error' => $result], 'noheader'); | ||||
|             } | ||||
|         } else { | ||||
|             $this->view('auth/signin', ['title' => 'Log In']); | ||||
|             $this->view('auth/signin', ['title' => 'Log In'], 'noheader'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -54,7 +54,7 @@ class AuthController extends Controller  { | ||||
|                 $this->view('auth/signup', [ | ||||
|                     'error' => 'Please correct the errors below.', | ||||
|                     'validationErrors' => $validator->errors() ?: [], | ||||
|                 ]); | ||||
|                 ], 'noheader'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -67,13 +67,13 @@ class AuthController extends Controller  { | ||||
|                 $this->view('auth/signup', [ | ||||
|                     'error' => $result, | ||||
|                     'validationErrors' => [], | ||||
|                 ]); | ||||
|                 ], 'noheader'); | ||||
|             } | ||||
|         } else { | ||||
|             $this->view('auth/signup', [ | ||||
|                 'title' => 'Register', | ||||
|                 'validationErrors' => [], | ||||
|             ]); | ||||
|             ], 'noheader'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
							
								
								
									
										42
									
								
								app/controllers/DashboardController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/controllers/DashboardController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <?php | ||||
| class DashboardController extends Controller { | ||||
|     public function index() { | ||||
|         $vehicle = new Vehicle(); | ||||
|         $vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']); | ||||
|  | ||||
|         $default_car = $vehicle->getDefaultVehicle($_SESSION['user']['id']); | ||||
|  | ||||
|         $refuel = new Refuel(); | ||||
|         $data = [ | ||||
|             "date" => [], | ||||
|             "price" => [], | ||||
|         ]; | ||||
|         $raw_data = $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']); | ||||
|         } | ||||
|  | ||||
|         $latest_record = [ | ||||
|             'name', | ||||
|             'liters', | ||||
|             'price_per_liter', | ||||
|             'total_price', | ||||
|             'created_at' | ||||
|         ]; | ||||
|  | ||||
|         $latest_record = $refuel->latest_one($_SESSION['user']['id'])[0]; | ||||
|  | ||||
|         $this->view('dashboard/index', [ | ||||
|             'title' => 'Dashboard', | ||||
|             'vehicles' => $vehicles, | ||||
|             'date_price_data' => $data, | ||||
|             'default_car' => $default_car, | ||||
|             'latest_record' => $latest_record, | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function reroute(){ | ||||
|         $this->redirect('/dashboard'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										85
									
								
								app/controllers/RefuelController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								app/controllers/RefuelController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| <?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'] ?? ''; | ||||
|             $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); | ||||
|  | ||||
|             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, | ||||
|             ]); | ||||
|  | ||||
|             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) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										61
									
								
								app/controllers/VehicleController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								app/controllers/VehicleController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| <?php | ||||
|  | ||||
| class VehicleController extends Controller { | ||||
|     public function index() { | ||||
|         $vehicle = new Vehicle(); | ||||
|         $vehicles = $vehicle->getVehiclesByUser($_SESSION['user']['id']); | ||||
|         $this->view('vehicles/index', ['title' => 'Vehicles', 'vehicles' => $vehicles]); | ||||
|     } | ||||
|  | ||||
|     public function create() { | ||||
|         if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
|             $name = $_POST['name'] ?? ''; | ||||
|             $registration_plate = $_POST['registration_plate'] ?? ''; | ||||
|             $fuel_type = $_POST['fuel_type'] ?? ''; | ||||
|             $note = $_POST['note'] ?? ''; | ||||
|  | ||||
|             $validator = new Validator(); | ||||
|             $validator->required('name', $name); | ||||
|             $validator->required('registration_plate', $registration_plate); | ||||
|             $validator->required('fuel_type', $fuel_type); | ||||
|  | ||||
|             if($note == "") $note = NULL; | ||||
|  | ||||
|             if (!$validator->passes()) { | ||||
|                 $this->view('vehicles/create', [ | ||||
|                     'error' => 'Please correct the errors below.', | ||||
|                     'validationErrors' => $validator->errors() ?: [], | ||||
|                 ]); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             $vehicle = new Vehicle(); | ||||
|             $result = $vehicle->create([ | ||||
|                 'name' => $name, | ||||
|                 'registration_plate' => strtoupper($registration_plate), | ||||
|                 'fuel_type' => $fuel_type, | ||||
|                 'note' => $note, | ||||
|                 'user_id' => $_SESSION['user']['id'], | ||||
|             ]); | ||||
|  | ||||
|  | ||||
|             if ($result === true) { | ||||
|                 $this->redirect('/vehicles'); | ||||
|             } else { | ||||
|                 $this->view('vehicles/create', ['title' => 'Create vehicle', 'error' => $result, 'validationErrors' => []] ); | ||||
|             } | ||||
|  | ||||
|         } else { | ||||
|             $this->view('vehicles/create', ['title' => 'Create Vehicle']); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public function edit() { | ||||
|         // Edit vehicle (to be implemented later) | ||||
|     } | ||||
|  | ||||
|     public function delete() { | ||||
|         // Delete vehicle (to be implemented later) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										92
									
								
								app/models/Refuel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								app/models/Refuel.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| <?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, created_at) | ||||
|                 VALUES (?, ?, ?, ?, ?, ?, ?, NOW()) | ||||
|             "); | ||||
|  | ||||
|             $stmt->bind_param( | ||||
|                 "iissddd", | ||||
|                 $data['user_id'], | ||||
|                 $data['vehicle_id'], | ||||
|                 $data['fuel_type'], | ||||
|                 $data['note'], | ||||
|                 $data['liters'], | ||||
|                 $data['price_per_liter'], | ||||
|                 $data['total_price'], | ||||
|             ); | ||||
|  | ||||
|             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`, `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($user_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`.`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 ?; | ||||
|             "); | ||||
|  | ||||
|             $stmt->bind_param("ii", $user_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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -42,7 +42,7 @@ class User { | ||||
|     public function login($email, $password) { | ||||
|         $hashedPassword = password_hash($password, PASSWORD_BCRYPT); | ||||
|  | ||||
|         $stmt = $this->db->prepare("SELECT username, password FROM users WHERE email = ?"); | ||||
|         $stmt = $this->db->prepare("SELECT id, username, password FROM users WHERE email = ?"); | ||||
|         $stmt->bind_param("s", $email); | ||||
|         $stmt->execute(); | ||||
|         $result = $stmt->get_result(); | ||||
| @@ -52,6 +52,7 @@ class User { | ||||
|             $user = $result->fetch_assoc(); | ||||
|             if (password_verify($password, $user['password'])) { | ||||
|                 $_SESSION['user'] = [ | ||||
|                     'id' => $user['id'], | ||||
|                     'username' => $user['username'], | ||||
|                     'email' => $email, | ||||
|                 ]; | ||||
| @@ -59,6 +60,6 @@ class User { | ||||
|             }  | ||||
|         }  | ||||
|  | ||||
|         return "Invalid email or password."; | ||||
|         return "Incorrect username or password."; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										63
									
								
								app/models/Vehicle.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/models/Vehicle.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| <?php | ||||
|  | ||||
| class Vehicle { | ||||
|     private $db; | ||||
|  | ||||
|     public function __construct() { | ||||
|         $this->db = Database::getInstance()->getConnection(); | ||||
|     } | ||||
|  | ||||
|     public function create($data) { | ||||
|         try{ | ||||
|             $stmt = $this->db->prepare(" | ||||
|                 INSERT INTO vehicles (user_id, name, registration_plate, fuel_type, note, created_at) | ||||
|                 VALUES (?, ?, ?, ?, ?, NOW()) | ||||
|             "); | ||||
|  | ||||
|             $stmt->bind_param( | ||||
|                 "issss", | ||||
|                 $data['user_id'], | ||||
|                 $data['name'], | ||||
|                 $data['registration_plate'], | ||||
|                 $data['fuel_type'], | ||||
|                 $data['note'], | ||||
|             ); | ||||
|  | ||||
|             if ($stmt->execute()) { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return "Error: " . $stmt->error; | ||||
|             } | ||||
|         } catch(mysqli_sql_exception $e) { | ||||
|             return $e->getMessage(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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->bind_param("i", $user_id); | ||||
|         $stmt->execute(); | ||||
|         $result = $stmt->get_result(); | ||||
|  | ||||
|         $vehicles = []; | ||||
|         while ($row = $result->fetch_assoc()) { | ||||
|             $vehicles[] = $row; | ||||
|         } | ||||
|          | ||||
|         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(); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,11 @@ | ||||
| <section class="signin"> | ||||
| <link rel="stylesheet" href="/css/login.css"> | ||||
| <link rel="stylesheet" href="/css/form.css"> | ||||
| <section class="form signin"> | ||||
|     <div class="header-form"> | ||||
|         <img src="/img/logo.jpg" alt="Fuel Stats Logo"> | ||||
|         <h1>Sign in to Fuel Stats</h1> | ||||
|     </div> | ||||
|  | ||||
|     <?php if ($this->get('error')): ?> | ||||
|         <div class="error"><?= $this->get('error') ?></div> | ||||
|     <?php endif; ?> | ||||
| @@ -18,4 +25,9 @@ | ||||
|  | ||||
|         <input type="submit" value="Sign In"> | ||||
|     </form> | ||||
|  | ||||
|     <div class="bordered"> | ||||
|         <p>New to Fuel Stats?</p> | ||||
|         <a href="/auth/signup">Create an account</a> | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| <section class="signup"> | ||||
| <link rel="stylesheet" href="/css/login.css"> | ||||
| <link rel="stylesheet" href="/css/form.css"> | ||||
| <section class="form signup"> | ||||
|     <div class="header-form"> | ||||
|         <img src="/img/logo.jpg" alt="Fuel Stats Logo"> | ||||
|         <h1>Sign up to Fuel Stats</h1> | ||||
|     </div> | ||||
|  | ||||
|     <?php if ($this->get('error')): ?> | ||||
|         <div class="error"><?= $this->get('error') ?></div> | ||||
|     <?php endif; ?> | ||||
| @@ -30,4 +37,9 @@ | ||||
|  | ||||
|         <input type="submit" value="Sign Up"> | ||||
|     </form> | ||||
|  | ||||
|     <div class="bordered"> | ||||
|         <p>Already have an account?</p> | ||||
|         <a href="/auth/signin">Sign in</a> | ||||
|     </div> | ||||
| </section> | ||||
|   | ||||
| @@ -1 +1,64 @@ | ||||
| <h1>Welcome <?= $_SESSION['user']['username']?>!</h1> | ||||
| <link rel="stylesheet" href="/css/dashboard.css"> | ||||
|  | ||||
| <section class="dashboard"> | ||||
|     <h1>Welcome, <?= htmlspecialchars($_SESSION['user']['username']) ?>!</h1> | ||||
|     <div> | ||||
|         <a href="/refuel/create" class="btn-green">Add new refuel record!</a> | ||||
|         <a href="/vehicles" class="btn-primary">List all vehicles</a> | ||||
|         <button class="btn-primary" id="btn-offline-add">Add new refuel record OFFLINE</button> | ||||
|     </div> | ||||
|     <div class="card-wrapper"> | ||||
|         <section class="card latest"> | ||||
|             <h2>Latest fuel record</h2> | ||||
|             <hr> | ||||
|             <div> | ||||
|                 <b>Car:</b> | ||||
|                 <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> | ||||
|             </div> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card"> | ||||
|             <h2>Default car</h2> | ||||
|             <hr> | ||||
|             <b>Car</b> | ||||
|             <p><?= $data['default_car']['name'] ?></p> | ||||
|             <b>Registration plate</b> | ||||
|             <p><?= $data['default_car']['registration_plate'] ?></p> | ||||
|             <b>Fuel type</b> | ||||
|             <p><?= $data['default_car']['fuel_type'] ?></p> | ||||
|             <b>Note</b> | ||||
|             <p><?= $data['default_car']['note'] ?></p> | ||||
|         </section> | ||||
|  | ||||
|         <section class="card history-graph"> | ||||
|             <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> | ||||
|     </div> | ||||
| </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 defer src="/js/offline-records.js"></script> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>Habit Tracker | Error 494</title> | ||||
|     <title>Fuel Stats | Error 494</title> | ||||
| </head> | ||||
| <body> | ||||
|     <h1>Error 404 - Page not found</h1> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <div> | ||||
|     <h1>Welcome to Habit Tracker!</h1> | ||||
|     <p>Track your habits and achieve your goals.</p> | ||||
|     <h1>Welcome to Fuel Stats!</h1> | ||||
|     <p>Keep track of your refuels.</p> | ||||
| </div> | ||||
|   | ||||
| @@ -1,17 +1,35 @@ | ||||
| <!doctype html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <title>Habit Tracker | <?= $data['title'] ?></title> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="author" content="Filip Rojek | http://filiprojek.cz"> | ||||
|     <meta name="email" content="webmaster(@)fofrweb.com"> | ||||
|     <meta name="copyright" content="(c) filiprojek.cz"> | ||||
|     <title>Fuel Stats<?= isset($data['title']) ? " | " . $data['title'] : "" ?></title> | ||||
|     <link rel="stylesheet" href="/css/main.css"> | ||||
|     <link rel="stylesheet" href="/css/global.css"> | ||||
|     <link rel="stylesheet" href="/css/vars.css"> | ||||
|     <link rel="stylesheet" href="/css/header.css"> | ||||
|     <link rel="icon" type="image/x-icon" href="/img/favicon.ico"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <header> | ||||
|       <a href="/auth/signin">Log In</a> | ||||
|       <a href="/auth/signup">Sign Up</a> | ||||
|       <a href="/auth/logout">Log Out</a> | ||||
|       <div id="hd-left"> | ||||
|         <a href="/"><img src="/img/logo.jpg" alt="home"></a> | ||||
|         <label><?= isset($data['title']) ? $data['title'] : "" ?></label> | ||||
|       </div> | ||||
|       <div id="hd-right"> | ||||
|         <?php if (!isset($_SESSION['user'])): ?> | ||||
|         <a href="/auth/signin">Log In</a> | ||||
|         <a href="/auth/signup">Sign Up</a> | ||||
|         <?php else: ?> | ||||
|         <a href="/auth/logout" class="btn-secondary">Sign out</a> | ||||
|         <?php endif; ?> | ||||
|       </div> | ||||
|     </header> | ||||
|     <section class="content"> | ||||
|     <main class="content"> | ||||
|       <?= $content ?> | ||||
|     </section> | ||||
|     </main> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										20
									
								
								app/views/layouts/noheader.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/views/layouts/noheader.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!doctype html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="author" content="Filip Rojek | http://filiprojek.cz"> | ||||
|     <meta name="email" content="webmaster(@)fofrweb.com"> | ||||
|     <meta name="copyright" content="(c) filiprojek.cz"> | ||||
|     <title>Fuel Stats<?= isset($data['title']) ? " | " . $data['title'] : "" ?></title> | ||||
|     <link rel="stylesheet" href="/css/main.css"> | ||||
|     <link rel="stylesheet" href="/css/global.css"> | ||||
|     <link rel="stylesheet" href="/css/vars.css"> | ||||
|     <link rel="icon" type="image/x-icon" href="/img/favicon.ico"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <main class="content"> | ||||
|       <?= $content ?> | ||||
|     </main> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										119
									
								
								app/views/refuel/create.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								app/views/refuel/create.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| <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="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> | ||||
							
								
								
									
										35
									
								
								app/views/vehicles/create.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/views/vehicles/create.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| <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', 'Create Vehicle') ?></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="/vehicles/create"> | ||||
|         <label for="name">Vehicle name</label> | ||||
|         <input type="text" name="name" id="name" required value="<?= htmlspecialchars($_POST['name'] ?? '') ?>"> | ||||
|  | ||||
|         <label for="registration_plate">Registration plate</label> | ||||
|         <input type="text" name="registration_plate" id="registration_plate" maxlength="10" onkeypress="return event.charCode != 32" required value="<?= htmlspecialchars($_POST['registration_plate'] ?? '') ?>"> | ||||
|  | ||||
|         <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> | ||||
|  | ||||
|         <label for="note">Note</label> | ||||
|         <input type="text" name="note" id="note" value="<?= htmlspecialchars($_POST['note'] ?? '') ?>"> | ||||
|  | ||||
|         <input type="submit" value="Create vehicle"> | ||||
|     </form> | ||||
| </section> | ||||
							
								
								
									
										24
									
								
								app/views/vehicles/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/views/vehicles/index.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| <link rel="stylesheet" href="/css/vehicles.css"> | ||||
| <section class="vehicles"> | ||||
|     <?php if (empty($this->get('vehicles'))): ?> | ||||
|         <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> | ||||
|         </div> | ||||
|         <div class="vehicle-wrapper"> | ||||
|         <?php foreach ($this->get('vehicles') as $vehicle): ?> | ||||
|             <div class="vehicle bordered"> | ||||
|                 <b><?= htmlspecialchars($vehicle['name']) ?></b> | ||||
|                 <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> | ||||
|             </div> | ||||
|         <?php endforeach; ?> | ||||
|         </div> | ||||
|     <?php endif; ?> | ||||
| </section> | ||||
							
								
								
									
										6
									
								
								config/environment.php.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								config/environment.php.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?php | ||||
|  | ||||
| define('DB_HOST', '0.0.0.0'); | ||||
| define('DB_NAME', 'fuel_stats');  | ||||
| define('DB_USER', 'username');  | ||||
| define('DB_PASS', 'password'); | ||||
| @@ -16,8 +16,8 @@ class Controller { | ||||
|      * @param string $viewName | ||||
|      * @param array $data | ||||
|      */ | ||||
|     public function view($viewName, $data = []) { | ||||
|     public function view($viewName, $data = [], $layout = "base") { | ||||
|         $view = new View(); | ||||
|         $view->render($viewName, $data); | ||||
|         $view->render($viewName, $data, $layout); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -57,7 +57,7 @@ class Database { | ||||
|      * Check and create required tables if they don't exist | ||||
|      */ | ||||
|     private function checkAndCreateTables() { | ||||
|         // Create users table | ||||
|         // Create tables | ||||
|         $usersTableQuery = "CREATE TABLE IF NOT EXISTS users ( | ||||
|             id INT AUTO_INCREMENT PRIMARY KEY, | ||||
|             username VARCHAR(50) NOT NULL, | ||||
| @@ -71,21 +71,56 @@ class Database { | ||||
|             die("Failed to create users table: " . $this->connection->error); | ||||
|         } | ||||
|  | ||||
|         // Create progress table | ||||
|         $progressTableQuery = "CREATE TABLE IF NOT EXISTS progress ( | ||||
|             id INT AUTO_INCREMENT PRIMARY KEY, | ||||
|             user_id INT NOT NULL, | ||||
|             habit_id INT NOT NULL, | ||||
|             date DATE NOT NULL, | ||||
|             status ENUM('Done', 'Pending') DEFAULT 'Pending', | ||||
|             FOREIGN KEY (user_id) REFERENCES users(id) | ||||
|         ) ENGINE=InnoDB;"; | ||||
|         $vehiclesTableQuery = " | ||||
|             CREATE TABLE IF NOT EXISTS vehicles ( | ||||
|                 id INT AUTO_INCREMENT PRIMARY KEY, | ||||
|                 user_id INT NOT NULL, | ||||
|                 name VARCHAR(100) NOT NULL, | ||||
|                 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, | ||||
|                 note VARCHAR(150) NULL, | ||||
|                 is_default BOOLEAN DEFAULT FALSE NOT NULL, | ||||
|                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | ||||
|                 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | ||||
|             ) ENGINE=InnoDB; | ||||
|         "; | ||||
|  | ||||
|         if (!$this->connection->query($progressTableQuery)) { | ||||
|             die("Failed to create progress table: " . $this->connection->error); | ||||
|         if (!$this->connection->query($vehiclesTableQuery)) { | ||||
|             die("Failed to create vehicles table: " . $this->connection->error); | ||||
|         } | ||||
|  | ||||
|         // Add more table creation logic as needed | ||||
|         $refuelingTableQuery = " | ||||
|             CREATE TABLE IF NOT EXISTS refueling_records ( | ||||
|                 id INT AUTO_INCREMENT PRIMARY KEY, | ||||
|                 user_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, | ||||
|                 note VARCHAR(150) NULL, | ||||
|                 liters DOUBLE(10, 2) NOT NULL, | ||||
|                 price_per_liter DOUBLE(10, 2) NOT NULL, | ||||
|                 total_price DOUBLE(10, 2) NOT 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 | ||||
|             ) ENGINE=InnoDB; | ||||
|         "; | ||||
|  | ||||
|         if (!$this->connection->query($refuelingTableQuery)) { | ||||
|             die("Failed to create refueling_records table: " . $this->connection->error); | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|         // Create table_name table | ||||
|         $usersTableQuery = "CREATE TABLE IF NOT EXISTS table_name ( | ||||
|             id INT AUTO_INCREMENT PRIMARY KEY, | ||||
|             username VARCHAR(50) NOT NULL, | ||||
|             created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | ||||
|         ) ENGINE=InnoDB;"; | ||||
|  | ||||
|         if (!$this->connection->query($usersTableQuery)) { | ||||
|             die("Failed to create table_name table: " . $this->connection->error); | ||||
|         } | ||||
|         */ | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| class Router { | ||||
|     private $routes = []; | ||||
|     private $middlewares = []; | ||||
|     private $groupPrefix = ''; | ||||
|     private $groupMiddlewares = []; | ||||
|  | ||||
|     /** | ||||
|      * Add a route with a specific action and optional middleware | ||||
| @@ -12,9 +14,35 @@ class Router { | ||||
|      * @param array $middlewares Optional middlewares for this route | ||||
|      */ | ||||
|     public function add($route, $action, $middlewares = []) { | ||||
|         $route = $this->groupPrefix . $route; | ||||
|         $middlewares = array_merge($this->groupMiddlewares, $middlewares); | ||||
|         $this->routes[$route] = ['action' => $action, 'middlewares' => $middlewares]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Define a group of routes with shared prefix and middlewares | ||||
|      *  | ||||
|      * @param string $prefix | ||||
|      * @param array $middlewares | ||||
|      * @param callable $callback | ||||
|      */ | ||||
|     public function group($prefix, $middlewares, $callback) { | ||||
|         // Save the current state | ||||
|         $previousPrefix = $this->groupPrefix; | ||||
|         $previousMiddlewares = $this->groupMiddlewares; | ||||
|  | ||||
|         // Set new group prefix and middlewares | ||||
|         $this->groupPrefix = $previousPrefix . $prefix; | ||||
|         $this->groupMiddlewares = array_merge($this->groupMiddlewares, $middlewares); | ||||
|  | ||||
|         // Execute the callback to define routes in the group | ||||
|         $callback($this); | ||||
|  | ||||
|         // Restore the previous state | ||||
|         $this->groupPrefix = $previousPrefix; | ||||
|         $this->groupMiddlewares = $previousMiddlewares; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Dispatch the current request to the correct route and execute middlewares | ||||
|      */ | ||||
| @@ -22,6 +50,11 @@ class Router { | ||||
|         $uri = $_SERVER['REQUEST_URI']; | ||||
|         $uri = parse_url($uri, PHP_URL_PATH); | ||||
|  | ||||
|         // Normalize the URI by removing trailing slash (except for root "/") | ||||
|         if ($uri !== '/' && substr($uri, -1) === '/') { | ||||
|             $uri = rtrim($uri, '/'); | ||||
|         } | ||||
|  | ||||
|         if (array_key_exists($uri, $this->routes)) { | ||||
|             $route = $this->routes[$uri]; | ||||
|             $middlewares = $route['middlewares']; | ||||
| @@ -45,7 +78,7 @@ class Router { | ||||
|         } else { | ||||
|             http_response_code(404); | ||||
|             $view = new View(); | ||||
|             $view->render('errors/404'); | ||||
|             $view->render('errors/404', [], 'noheader'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|      */ | ||||
| @@ -46,6 +55,10 @@ class Validator { | ||||
|         return $this->errors; | ||||
|     } | ||||
|  | ||||
|     public function setErrors($errors) { | ||||
|         $this->errors = $errors; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check if validation passed | ||||
|      */ | ||||
|   | ||||
| @@ -1,16 +1,31 @@ | ||||
| services: | ||||
|   mariadb: | ||||
|     image: mariadb:11.4 # LTS at 25. 12. 2025 | ||||
|     restart: always | ||||
|     restart: on-failure:2 | ||||
|     environment: | ||||
|       MARIADB_ROOT_PASSWORD: root | ||||
|     ports: | ||||
|       - 3306:3306 | ||||
|     profiles: ["prod", "dev"] | ||||
|  | ||||
|   phpmyadmin: | ||||
|     image: phpmyadmin | ||||
|     restart: always | ||||
|     restart: on-failure:2 | ||||
|     ports: | ||||
|       - 8080:80 | ||||
|     environment: | ||||
|       - PMA_ARBITRARY=1 | ||||
|     profiles: ["dev"] | ||||
|  | ||||
|   fuelstats: | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: Dockerfile | ||||
|     volumes: | ||||
|       - .:/var/www/html | ||||
|     ports: | ||||
|       - 8000:80 | ||||
|     depends_on: | ||||
|       - mariadb | ||||
|     restart: on-failure:2 | ||||
|     profiles: ["prod"] | ||||
|   | ||||
							
								
								
									
										21
									
								
								public/css/dashboard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								public/css/dashboard.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| .dashboard { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .card-wrapper { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 1rem; | ||||
|   justify-content: center; | ||||
|   margin-top: 2rem; | ||||
| } | ||||
|  | ||||
| .card { | ||||
|   background-color: var(--clr-secondary); | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 18rem; | ||||
|   padding: 1rem; | ||||
| } | ||||
							
								
								
									
										78
									
								
								public/css/form.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								public/css/form.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| .form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   background-color: var(--clr-secondary); | ||||
| } | ||||
|  | ||||
| .form .header-form { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   flex-direction: column; | ||||
| } | ||||
|  | ||||
| .form .header-form img { | ||||
|   height: 5rem; | ||||
| } | ||||
|  | ||||
| .form form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding: 1rem; | ||||
|   background-color: var(--clr-tertiary); | ||||
|   gap: .5rem; | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
| } | ||||
|  | ||||
| .form form input, | ||||
| select { | ||||
|   background-color: var(--clr-secondary); | ||||
|   caret-color: white; | ||||
|   color: white; | ||||
|   padding: .3rem; | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 15rem; | ||||
| } | ||||
|  | ||||
| .form form input[type="submit"] { | ||||
|   background-color: var(--clr-green); | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| .form .error { | ||||
|   width: 17rem; | ||||
|   padding: 1rem; | ||||
|   background-color: var(--clr-danger-muted); | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border-danger); | ||||
|   margin-bottom: 1rem; | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| .form small.error { | ||||
|   width: 15rem; | ||||
| } | ||||
|  | ||||
| .form .bordered { | ||||
|   border-radius: var(--border-radious); | ||||
|   border: var(--borderWidth-thin) solid var(--clr-border); | ||||
|   width: 17rem; | ||||
|   padding: 1rem; | ||||
|   margin-top: 1rem; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .form .bordered a { | ||||
|   text-decoration: none; | ||||
|   color: var(--clr-link-blue); | ||||
| } | ||||
|  | ||||
| .form .bordered a:hover { | ||||
|   text-decoration: underline; | ||||
| } | ||||
							
								
								
									
										48
									
								
								public/css/global.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								public/css/global.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| @import url("https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"); | ||||
|  | ||||
| body { | ||||
| 	font-family: "Open Sans", serif; | ||||
| 	background-color: var(--clr-primary); | ||||
| 	color: white; | ||||
| 	font-size: 14px; | ||||
| } | ||||
|  | ||||
| a { | ||||
| 	color: white; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
| 	margin-top: .5rem; | ||||
| 	margin-bottom: 1rem; | ||||
| } | ||||
|  | ||||
| .btn-primary, | ||||
| .btn-secondary, | ||||
| .btn-tertiary, | ||||
| .btn-green, | ||||
| .btn-danger { | ||||
| 	background-color: var(--clr-primary); | ||||
| 	padding: .5rem; | ||||
| 	text-decoration: none; | ||||
| 	cursor: pointer; | ||||
| 	border-radius: var(--border-radious); | ||||
| 	border: var(--borderWidth-thin) solid var(--clr-border); | ||||
| 	color: white; | ||||
| } | ||||
|  | ||||
| .btn-secondary { | ||||
| 	background-color: var(--clr-secondary); | ||||
| } | ||||
|  | ||||
| .btn-tertiary { | ||||
| 	background-color: var(--clr-tertiary); | ||||
| } | ||||
|  | ||||
| .btn-green { | ||||
| 	background-color: var(--clr-green); | ||||
| } | ||||
|  | ||||
| .btn-danger { | ||||
| 	background-color: var(--clr-danger-muted); | ||||
| 	border: var(--borderWidth-thin) solid var(--clr-border-danger); | ||||
| } | ||||
							
								
								
									
										26
									
								
								public/css/header.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								public/css/header.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| header { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
|   padding: 0 2rem; | ||||
|   width: 100vw; | ||||
|   height: 3rem; | ||||
|   background: var(--clr-secondary); | ||||
|   border-radius: var(--border-radious); | ||||
|   border-bottom: var(--borderWidth-thin) solid var(--clr-border); | ||||
| } | ||||
|  | ||||
| #hd-left, | ||||
| #hd-right { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 1rem; | ||||
| } | ||||
|  | ||||
| header a img { | ||||
|   height: 2rem; | ||||
| } | ||||
|  | ||||
| header a { | ||||
|   text-decoration: none; | ||||
| } | ||||
							
								
								
									
										13
									
								
								public/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								public/css/main.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| main { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   margin-top: 2rem; | ||||
|   margin-bottom: 2rem; | ||||
|   padding: 0 var(--container-size); | ||||
| } | ||||
							
								
								
									
										20
									
								
								public/css/vars.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								public/css/vars.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| :root { | ||||
|   --container-size: 5vw; | ||||
|   --clr-primary: #010409; | ||||
|   --clr-secondary: #0d1117; | ||||
|   --clr-tertiary: #151b23; | ||||
|   --clr-green: #238636; | ||||
|   --clr-danger-muted: #f851491a; | ||||
|  | ||||
|   --clr-link-blue: #4493f8; | ||||
|   --clr-light-blue: #39a2ae; | ||||
|   --clr-light-green: #71f79f; | ||||
|   --clr-red: #9b1d20; | ||||
|   --clr-orange: #e08e45; | ||||
|   --clr-gray-blue: #627c85; | ||||
|  | ||||
|   --border-radious: 5px; | ||||
|   --borderWidth-thin: max(1px, 0.0625rem); | ||||
|   --clr-border: #3d444db3; | ||||
|   --clr-border-danger: #f8514966; | ||||
| } | ||||
							
								
								
									
										3
									
								
								public/css/vehicle_create.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								public/css/vehicle_create.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #registration_plate { | ||||
|   text-transform: uppercase; | ||||
| } | ||||
							
								
								
									
										29
									
								
								public/css/vehicles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								public/css/vehicles.css
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								public/img/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 45 KiB | 
| @@ -20,15 +20,37 @@ require_once '../core/Database.php'; | ||||
| require_once '../core/middlewares/RequireAuth.php'; | ||||
|  | ||||
| require_once models . 'User.php'; | ||||
| require_once models . 'Vehicle.php'; | ||||
| require_once models . 'Refuel.php'; | ||||
|  | ||||
| // Initialize router | ||||
| $router = new Router(); | ||||
| $router->add('/', 'HomeController@index'); | ||||
| $router->add('/home', 'HomeController@home'); | ||||
| $router->add('/dashboard', 'HomeController@dashboard', ['RequireAuth']); | ||||
| if(!isset($_SESSION['user'])) { | ||||
|     $router->add('/', 'HomeController@index'); | ||||
| } else { | ||||
|     $router->add('/', 'DashboardController@reroute', ['RequireAuth']); | ||||
| } | ||||
|  | ||||
| // auth routes | ||||
| $router->add('/auth/signin', 'AuthController@signin'); | ||||
| $router->add('/auth/signup', 'AuthController@signup'); | ||||
| $router->add('/auth/logout', 'AuthController@logout'); | ||||
| $router->group('/auth', [], function ($router) { | ||||
|     $router->add('/signin', 'AuthController@signin'); | ||||
|     $router->add('/signup', 'AuthController@signup'); | ||||
|     $router->add('/logout', 'AuthController@logout'); | ||||
| }); | ||||
|  | ||||
| // dashboard route | ||||
| $router->add('/dashboard', 'DashboardController@index', ['RequireAuth']); | ||||
|  | ||||
| // vehicle routes | ||||
| $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->group('/refuel', ['RequireAuth'], function ($router) { | ||||
|     $router->add('/create', 'RefuelController@create'); | ||||
| }); | ||||
|  | ||||
| $router->dispatch(); | ||||
|   | ||||
							
								
								
									
										98
									
								
								public/js/offline-records.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								public/js/offline-records.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| 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; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| setInterval(async () => { | ||||
| 	const isOnline = await checkOnline(); | ||||
| 	if (!isOnline) { | ||||
| 		console.log("OFFLINE!!!"); | ||||
| 	} | ||||
| }, 5000); | ||||
|  | ||||
| const offbtn = document.querySelector("#btn-offline-add"); | ||||
| offbtn.addEventListener("click", (e) => { | ||||
| 	e.preventDefault(); | ||||
| 	document.querySelector("section.dashboard").style.display = "none"; | ||||
| 	const offline = document.createElement("div"); | ||||
| 	offline.classList.add("offline"); | ||||
| 	offline.innerHTML = ` | ||||
|     <b>You're Offline</b> | ||||
|     <p>You can create an fuel record locally on your device and sync it later</p> | ||||
|     <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="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> | ||||
|   `; | ||||
| 	document.querySelector("main").appendChild(offline); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user