Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9f1a115d2 | |||
| a1d6afe503 | |||
| 30d45c59a9 | |||
| 2eaedab29f | |||
| 6fc7d2b005 | |||
| 83cc38d471 | |||
| 7555303036 | |||
| ac350238cd | |||
| f39c76e291 | |||
| fa0ad72cac | |||
| 6d73ab0592 | |||
| df7baa8276 | |||
| 09565eb3d6 | |||
| 16e29a9b49 | |||
| bea833a5b7 | |||
| ac98a7714a | |||
| b0dec9d3e3 | |||
| 444c94c898 | |||
| a4cf82a470 | |||
| cce8dacea6 | |||
| 4b6315ce6b | |||
| 90e076d94c | |||
| 83fa46f264 | |||
| ea417295f9 | |||
| d3682aaa94 | |||
| 7440004210 | |||
| d0a0d7a0b7 |
@@ -22,3 +22,6 @@ Thumbs.db
|
|||||||
|
|
||||||
# htaccess
|
# htaccess
|
||||||
.htaccess
|
.htaccess
|
||||||
|
|
||||||
|
# Local worktrees
|
||||||
|
.worktrees/
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
Agent guidance for working in this repository (`mainty`).
|
||||||
|
|
||||||
|
## Project Snapshot
|
||||||
|
|
||||||
|
- Stack: PHP 8+, SQLite, Apache, Tailwind via CDN, Bootstrap Icons via CDN.
|
||||||
|
- Architecture: lightweight MVC-style structure (no Composer, no framework).
|
||||||
|
- Entry point: `index.php`.
|
||||||
|
- Routing: `core/Router.php` maps routes to `Controller@method`.
|
||||||
|
- Data layer: PDO SQLite through singleton in `core/Database.php`.
|
||||||
|
- Runtime config: constants and debug flags in `config.php`.
|
||||||
|
- Deployment option: Docker (`Dockerfile`, `docker-compose.yml`) or traditional Apache/PHP host.
|
||||||
|
|
||||||
|
## Repository Layout
|
||||||
|
|
||||||
|
- `index.php`: bootstrap, environment checks, route registration, request dispatch.
|
||||||
|
- `core/`: base infrastructure (`Controller`, `Router`, `Database`).
|
||||||
|
- `controllers/`: request handlers, auth/setup guards, redirects.
|
||||||
|
- `models/`: database queries and persistence logic.
|
||||||
|
- `views/`: server-rendered PHP templates and inline JS.
|
||||||
|
- `data/`: SQLite DB location (`data/mainty.db`), ignored by Git.
|
||||||
|
- `README.md`: user-facing setup notes.
|
||||||
|
- `DOCKER.md`: Docker workflow and troubleshooting.
|
||||||
|
|
||||||
|
## Build, Lint, and Test Commands
|
||||||
|
|
||||||
|
This repo currently has no formal package manager scripts (`composer.json`, `package.json`) and no configured PHPUnit/Pest suite.
|
||||||
|
|
||||||
|
### Local runtime checks
|
||||||
|
|
||||||
|
- Start app with Docker:
|
||||||
|
- `docker-compose up -d`
|
||||||
|
- Rebuild and start:
|
||||||
|
- `docker-compose up -d --build`
|
||||||
|
- Stop containers:
|
||||||
|
- `docker-compose down`
|
||||||
|
- Follow logs:
|
||||||
|
- `docker-compose logs -f`
|
||||||
|
- Open app:
|
||||||
|
- `http://localhost:8080`
|
||||||
|
|
||||||
|
### Linting (available today)
|
||||||
|
|
||||||
|
- Lint a single PHP file:
|
||||||
|
- `php -l path/to/file.php`
|
||||||
|
- Lint all PHP files from repo root:
|
||||||
|
- `find . -name "*.php" -print0 | xargs -0 -n1 php -l`
|
||||||
|
|
||||||
|
### Tests (current state)
|
||||||
|
|
||||||
|
- There is no automated test suite committed in this repository at this time.
|
||||||
|
- Use targeted syntax checks plus manual verification in browser for affected flows.
|
||||||
|
|
||||||
|
### Single-test guidance
|
||||||
|
|
||||||
|
- Since no test framework is configured, there is no native "run one unit test" command yet.
|
||||||
|
- Closest equivalent today:
|
||||||
|
- Run syntax check on changed file: `php -l path/to/changed.php`
|
||||||
|
- Validate one user flow manually (for example setup/login/add vehicle).
|
||||||
|
- If PHPUnit is introduced later, single-test pattern should be:
|
||||||
|
- `vendor/bin/phpunit tests/Path/To/TestFile.php`
|
||||||
|
- or `vendor/bin/phpunit --filter testMethodName`
|
||||||
|
|
||||||
|
## Development Workflow for Agents
|
||||||
|
|
||||||
|
- Prefer small, focused edits in the relevant layer (controller/model/view).
|
||||||
|
- Preserve existing architecture; avoid introducing frameworks or heavy abstractions.
|
||||||
|
- Keep route definitions centralized in `index.php` unless project conventions change.
|
||||||
|
- For DB changes, update `Database::initialize()` schema and ensure migration path for existing DBs.
|
||||||
|
- Validate impact on setup/auth guards (`requireSetup()`, `requireAuth()`).
|
||||||
|
- When touching rendered output, maintain existing Tailwind + Bootstrap Icons approach.
|
||||||
|
|
||||||
|
## Code Style and Conventions
|
||||||
|
|
||||||
|
The project does not include an enforced formatter. Follow established in-repo patterns.
|
||||||
|
|
||||||
|
### PHP formatting
|
||||||
|
|
||||||
|
- Use 4-space indentation; no tabs.
|
||||||
|
- Opening braces are on the same line for classes, methods, and control structures.
|
||||||
|
- Keep method visibility explicit (`public`, `private`, `protected`).
|
||||||
|
- Prefer strict return types and parameter types where already used.
|
||||||
|
- Add blank lines between logical blocks to keep controllers readable.
|
||||||
|
|
||||||
|
### Imports and file organization
|
||||||
|
|
||||||
|
- There are no namespaces and no `use` imports currently.
|
||||||
|
- Autoloading is manual via `spl_autoload_register` in `index.php`.
|
||||||
|
- New class files should be placed in one of:
|
||||||
|
- `core/`
|
||||||
|
- `controllers/`
|
||||||
|
- `models/`
|
||||||
|
- Class name must match filename (e.g., `VehicleController` in `controllers/VehicleController.php`).
|
||||||
|
|
||||||
|
### Types and data handling
|
||||||
|
|
||||||
|
- Follow existing scalar type hints (`string`, `int`, `array`, `bool`, `void`).
|
||||||
|
- For IDs from route params, cast to int in controllers before model calls.
|
||||||
|
- Treat user input as untrusted; trim and validate before persistence.
|
||||||
|
- Use nullable values consistently when optional fields are absent.
|
||||||
|
|
||||||
|
### Naming conventions
|
||||||
|
|
||||||
|
- Classes: PascalCase (`MaintenanceController`, `QuickTask`).
|
||||||
|
- Methods/functions: camelCase (`changePassword`, `getByVehicleId`).
|
||||||
|
- Variables/properties: camelCase (`$maintenanceItems`, `$quickTaskModel`).
|
||||||
|
- DB columns: snake_case (`license_plate`, `updated_at`).
|
||||||
|
- Route paths: kebab/snake mix already exists; follow existing route patterns.
|
||||||
|
|
||||||
|
### Database and SQL conventions
|
||||||
|
|
||||||
|
- Use prepared statements for variable-bound SQL (existing standard).
|
||||||
|
- Keep SQL readable with multiline strings where complex.
|
||||||
|
- Return arrays from model queries (`fetchAll()`/`fetch()`) per current style.
|
||||||
|
- Keep persistence logic inside models; avoid SQL in controllers/views.
|
||||||
|
|
||||||
|
### Error handling and user feedback
|
||||||
|
|
||||||
|
- Controllers use session flash messages (`$_SESSION['error']`, `$_SESSION['success']`).
|
||||||
|
- Redirect after mutations instead of rendering directly.
|
||||||
|
- For JSON endpoints, return structured JSON via `Controller::json()`.
|
||||||
|
- Internal failures are currently handled with basic `die()` or fallback messages.
|
||||||
|
- Do not leak sensitive data in production; respect `DEBUG` flag in `config.php`.
|
||||||
|
|
||||||
|
### Security and output escaping
|
||||||
|
|
||||||
|
- Escape user-facing output in views with `htmlspecialchars()`.
|
||||||
|
- Keep password handling through `password_hash()` and `password_verify()`.
|
||||||
|
- Maintain auth checks on protected routes.
|
||||||
|
- Be cautious with direct echo of exception messages.
|
||||||
|
|
||||||
|
### Frontend conventions in views
|
||||||
|
|
||||||
|
- Server-rendered PHP templates with inline Tailwind classes.
|
||||||
|
- Keep interactions lightweight with inline `<script>` blocks per page.
|
||||||
|
- Reuse visual language already present (cards, rounded corners, blue primary actions).
|
||||||
|
- Preserve responsiveness (mobile-first classes used throughout existing views).
|
||||||
|
|
||||||
|
## Cursor / Copilot Instruction Files
|
||||||
|
|
||||||
|
Checked locations requested by user:
|
||||||
|
|
||||||
|
- `.cursor/rules/`: not present in this repository.
|
||||||
|
- `.cursorrules`: not present in this repository.
|
||||||
|
- `.github/copilot-instructions.md`: not present in this repository.
|
||||||
|
|
||||||
|
If any of these files are added later, treat them as high-priority agent instructions and update this document accordingly.
|
||||||
|
|
||||||
|
## Verification Checklist Before Finishing a Change
|
||||||
|
|
||||||
|
- Run `php -l` on each changed PHP file.
|
||||||
|
- If Dockerized workflow is relevant, run `docker-compose up -d` and load the touched page.
|
||||||
|
- Confirm setup/auth redirects still behave correctly.
|
||||||
|
- Confirm flash messages and redirects still match expected UX.
|
||||||
|
- Avoid committing generated DB files or secrets (`data/*.db`, `.env`).
|
||||||
@@ -126,8 +126,6 @@ For production:
|
|||||||
Example production `docker-compose.yml`:
|
Example production `docker-compose.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mainty:
|
mainty:
|
||||||
build: .
|
build: .
|
||||||
@@ -136,12 +134,9 @@ services:
|
|||||||
- "8080:80"
|
- "8080:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/var/www/html/data
|
- ./data:/var/www/html/data
|
||||||
|
# Uncomment below to sync code changes in development
|
||||||
|
# - .:/var/www/html
|
||||||
environment:
|
environment:
|
||||||
- APACHE_DOCUMENT_ROOT=/var/www/html
|
- APACHE_DOCUMENT_ROOT=/var/www/html
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
logging:
|
|
||||||
driver: "json-file"
|
|
||||||
options:
|
|
||||||
max-size: "10m"
|
|
||||||
max-file: "3"
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,16 +1,33 @@
|
|||||||
# Mainty
|
# Mainty
|
||||||
|
|
||||||
A simple PHP web app for tracking vehicle maintenance records. Free, simple, open source, and self-hosted. Runs on any Apache/PHP web server, or use Docker. Uses SQLite for easy backup, with built-in Export via JSON or HTML so you can import that data into something else or print records for your mechanic or the next owner of your vehicle.
|
A simple PHP web app for tracking vehicle maintenance records. Free, easy, responsive, open source, and self-hosted. Use Docker, or host it on any Apache/PHP web server. Uses SQLite for easy backup, with built-in Export via JSON or HTML so you can import that data into something else or print records for your mechanic or the next owner of your vehicle.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
- If you're using Docker, these requirements should be handled automatically, and you don't need to worry about them:
|
||||||
- Apache web server
|
- Apache web server
|
||||||
- PHP 8 or higher
|
- PHP 8 or higher
|
||||||
- SQLite extension
|
- SQLite extension
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Option 1: Traditional Web Server
|
### Option 1: Docker
|
||||||
|
|
||||||
|
Once you have Docker working on your system, enter the directory where you have placed Mainty and run the following command to start the services:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://localhost:8080
|
||||||
|
|
||||||
|
When you are done using Mainty, you can run the following command to stop the services:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Traditional Web Server
|
||||||
|
|
||||||
1. Upload the entire folder to your web server
|
1. Upload the entire folder to your web server
|
||||||
2. Rename `example.htaccess` to `.htaccess`
|
2. Rename `example.htaccess` to `.htaccess`
|
||||||
@@ -22,18 +39,6 @@ A simple PHP web app for tracking vehicle maintenance records. Free, simple, ope
|
|||||||
5. If everything is configured correctly, you'll see the setup page
|
5. If everything is configured correctly, you'll see the setup page
|
||||||
6. Set your password to initialize the database
|
6. Set your password to initialize the database
|
||||||
|
|
||||||
### Option 2: Docker
|
## Need help? Want to learn more?
|
||||||
|
|
||||||
```bash
|
https://michaelstaake.com/projects/mainty/
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open http://localhost:8080
|
|
||||||
|
|
||||||
## First Time Setup
|
|
||||||
|
|
||||||
When you first access the app, you'll be prompted to:
|
|
||||||
1. Create a password
|
|
||||||
2. Initialize the database
|
|
||||||
|
|
||||||
That's it! You're ready to start tracking your vehicle maintenance.
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mainty:
|
mainty:
|
||||||
build: .
|
build: .
|
||||||
|
|||||||
@@ -0,0 +1,642 @@
|
|||||||
|
# Dark Mode Support Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add cookie-persisted dark/light theming with a floating top-right toggle on all regular views, while keeping export always light.
|
||||||
|
|
||||||
|
**Architecture:** Theme behavior is centralized in one reusable partial with two include modes (`head` and `body`) so every themed page gets identical bootstrap, cookie logic, and toggle UI. Existing views keep structure/layout classes and only adjust color-related classes needed for dark-mode readability. Export is explicitly excluded and remains light-only.
|
||||||
|
|
||||||
|
**Tech Stack:** PHP 8, server-rendered PHP views, Tailwind via CDN, Bootstrap Icons, vanilla JavaScript, browser cookies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chunk 1: Shared Theme Unit and Core Integration
|
||||||
|
|
||||||
|
### Task 1: Create shared theme partial with explicit section contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `views/partials/theme.php`
|
||||||
|
- Test: browser manual validation on `/login` after integration
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add section-gated partial skeleton**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$themeSection = $themeSection ?? null;
|
||||||
|
|
||||||
|
if ($themeSection === 'head') {
|
||||||
|
// output head-only theme bootstrap and shared CSS
|
||||||
|
} elseif ($themeSection === 'body') {
|
||||||
|
// output body-only floating toggle and behavior script
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement `head` output with early theme bootstrap script**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
function readThemeCookie() {
|
||||||
|
var match = document.cookie.match(/(?:^|; )theme=([^;]+)/);
|
||||||
|
if (!match) return null;
|
||||||
|
var value = decodeURIComponent(match[1]);
|
||||||
|
return (value === 'dark' || value === 'light') ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookieTheme = readThemeCookie();
|
||||||
|
var systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
var resolved = cookieTheme || (systemDark ? 'dark' : 'light');
|
||||||
|
|
||||||
|
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
||||||
|
document.documentElement.setAttribute('data-theme', resolved);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
- `<html>` has `dark` class before body render when resolved theme is dark.
|
||||||
|
- `data-theme` is set to `dark` or `light`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement `head` shared CSS tokens and semantic utility classes**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--theme-bg: #f3f4f6;
|
||||||
|
--theme-surface: #ffffff;
|
||||||
|
--theme-surface-alt: #f9fafb;
|
||||||
|
--theme-text: #1f2937;
|
||||||
|
--theme-text-muted: #4b5563;
|
||||||
|
--theme-border: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
--theme-bg: #0f172a;
|
||||||
|
--theme-surface: #111827;
|
||||||
|
--theme-surface-alt: #1f2937;
|
||||||
|
--theme-text: #e5e7eb;
|
||||||
|
--theme-text-muted: #9ca3af;
|
||||||
|
--theme-border: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-page { background-color: var(--theme-bg); color: var(--theme-text); }
|
||||||
|
.theme-surface { background-color: var(--theme-surface); color: var(--theme-text); }
|
||||||
|
.theme-surface-alt { background-color: var(--theme-surface-alt); }
|
||||||
|
.theme-text { color: var(--theme-text); }
|
||||||
|
.theme-text-muted { color: var(--theme-text-muted); }
|
||||||
|
.theme-border { border-color: var(--theme-border); }
|
||||||
|
.theme-input { background-color: var(--theme-surface); color: var(--theme-text); border-color: var(--theme-border); }
|
||||||
|
|
||||||
|
#themeToggle {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
#themeToggle:focus-visible {
|
||||||
|
outline: 2px solid #2563eb;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
- Shared classes exist for page/surface/text/border/input mappings.
|
||||||
|
- Toggle has fixed top-right placement and z-index 50.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Implement `body` output with toggle markup and no-JS fallback**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<button id="themeToggle" type="button" aria-label="Switch to dark mode" title="Switch to dark mode">
|
||||||
|
<i id="themeToggleIcon" class="bi bi-moon-stars-fill"></i>
|
||||||
|
</button>
|
||||||
|
<noscript><style>#themeToggle{display:none;}</style></noscript>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Implement `body` behavior script with cookie helpers and state sync**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var toggle = document.getElementById('themeToggle');
|
||||||
|
var icon = document.getElementById('themeToggleIcon');
|
||||||
|
|
||||||
|
function readThemeCookie() {
|
||||||
|
var match = document.cookie.match(/(?:^|; )theme=([^;]+)/);
|
||||||
|
if (!match) return null;
|
||||||
|
var value = decodeURIComponent(match[1]);
|
||||||
|
return (value === 'dark' || value === 'light') ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeThemeCookie(theme) {
|
||||||
|
document.cookie = 'theme=' + encodeURIComponent(theme) + '; max-age=31536000; path=/; SameSite=Lax';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
var isDark = theme === 'dark';
|
||||||
|
document.documentElement.classList.toggle('dark', isDark);
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncToggle(theme) {
|
||||||
|
var isDark = theme === 'dark';
|
||||||
|
icon.className = isDark ? 'bi bi-sun-fill' : 'bi bi-moon-stars-fill';
|
||||||
|
var nextAction = isDark ? 'Switch to light mode' : 'Switch to dark mode';
|
||||||
|
toggle.setAttribute('aria-label', nextAction);
|
||||||
|
toggle.setAttribute('title', nextAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : (readThemeCookie() || 'light');
|
||||||
|
syncToggle(currentTheme);
|
||||||
|
|
||||||
|
toggle.addEventListener('click', function() {
|
||||||
|
currentTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||||
|
applyTheme(currentTheme);
|
||||||
|
writeThemeCookie(currentTheme);
|
||||||
|
syncToggle(currentTheme);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
- Click toggles theme immediately, updates icon, `aria-label`, and `title`.
|
||||||
|
- Cookie write attempt does not block visual switch if persistence is restricted.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run syntax check for new partial**
|
||||||
|
|
||||||
|
Run: `php -l views/partials/theme.php`
|
||||||
|
|
||||||
|
Expected: `No syntax errors detected in views/partials/theme.php`.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit Task 1**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add views/partials/theme.php
|
||||||
|
git commit -m "feat: add shared theme partial with cookie-based dark mode"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 2: Integrate theme partial into login and setup views
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `views/login.php`
|
||||||
|
- Modify: `views/setup.php`
|
||||||
|
- Test: `/login`, `/setup`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Insert head include in `views/login.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement: in `<head>`, after Tailwind/bootstrap includes, before `</head>`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Insert body include in `views/login.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement: before `</body>`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Apply color-only updates in `views/login.php`**
|
||||||
|
|
||||||
|
Apply concrete replacements:
|
||||||
|
- `<body class="bg-gray-100 ...">` -> `<body class="theme-page ...">`
|
||||||
|
- login card `bg-white` -> `theme-surface`
|
||||||
|
- heading `text-gray-800` -> `theme-text`
|
||||||
|
- label text `text-gray-700` -> `theme-text`
|
||||||
|
- input `border-gray-300` -> add `theme-input theme-border`
|
||||||
|
|
||||||
|
Expected outcome: login card, labels, and input fields are readable in both themes without layout shift.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Insert head include in `views/setup.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement: in `<head>`, after Tailwind/bootstrap includes, before `</head>`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Insert body include in `views/setup.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement: before `</body>`.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Apply color-only updates in `views/setup.php`**
|
||||||
|
|
||||||
|
Apply concrete replacements:
|
||||||
|
- `<body class="bg-gray-100 ...">` -> `<body class="theme-page ...">`
|
||||||
|
- setup card `bg-white` -> `theme-surface`
|
||||||
|
- title and labels `text-gray-*` -> `theme-text` / `theme-text-muted`
|
||||||
|
- password inputs `border-gray-300` -> add `theme-input theme-border`
|
||||||
|
|
||||||
|
Expected outcome: setup page remains visually consistent, with readable text/inputs in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run syntax checks for auth/setup views**
|
||||||
|
|
||||||
|
Run: `php -l views/login.php && php -l views/setup.php`
|
||||||
|
|
||||||
|
Expected: both report `No syntax errors detected`.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Manual verification on `/login` and `/setup`**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- toggle appears fixed top-right
|
||||||
|
- keyboard Tab focuses toggle with visible focus style
|
||||||
|
- Enter/Space toggles theme
|
||||||
|
- icon and `aria-label`/`title` change each toggle
|
||||||
|
- reload preserves preference
|
||||||
|
|
||||||
|
- [ ] **Step 9: Commit Task 2**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add views/login.php views/setup.php
|
||||||
|
git commit -m "feat: integrate dark mode toggle on auth and setup views"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chunk 2: Main Views, Export Exception, and Final Verification
|
||||||
|
|
||||||
|
### Task 3: Integrate theme partial into home view
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `views/index.php`
|
||||||
|
- Test: `/home`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add head include in `views/index.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: after Tailwind/bootstrap includes and before `</head>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one head include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add body include in `views/index.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: before local page scripts and before `</body>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one body include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update page and header surfaces in `views/index.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- body wrapper `bg-gray-100` -> `theme-page`
|
||||||
|
- header/card wrappers `bg-white` -> `theme-surface`
|
||||||
|
- heading/body text `text-gray-*` -> `theme-text` / `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: page shell and header are readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update table surfaces in `views/index.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- table header `<thead class="bg-gray-50 border-b">` -> use `theme-surface-alt theme-border`
|
||||||
|
- table body divider `divide-gray-200` -> `theme-border`
|
||||||
|
- table cell text classes `text-gray-500`, `text-gray-600`, `text-gray-900` -> `theme-text`/`theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: table head/body text and borders remain readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Update vehicle cards/list rows in `views/index.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- grid cards `bg-white` -> `theme-surface`
|
||||||
|
- list container `bg-white` -> `theme-surface`
|
||||||
|
- row hover `hover:bg-gray-50` -> `hover:opacity-95` and apply `theme-surface-alt` on row container where needed
|
||||||
|
- small metadata text `text-gray-*` -> `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: vehicle cards/list rows remain readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Update modal container and input surfaces in `views/index.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- modal panel `bg-white` -> `theme-surface`
|
||||||
|
- modal close/cancel gray text -> `theme-text-muted`
|
||||||
|
- inputs with `border-gray-300` -> add `theme-input theme-border`
|
||||||
|
- optional helper text gray shades -> `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: cards, table, and modal inputs remain readable/usable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Verify and adjust flash message contrast in `views/index.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- keep existing light-mode classes for base state (`bg-red-100 border-red-400 text-red-700`, `bg-green-100 border-green-400 text-green-700`)
|
||||||
|
- add dark-only variants (using `dark:` classes or equivalent conditional class strategy) for error/success contrast in dark mode
|
||||||
|
- keep existing rounded/padding/layout classes unchanged
|
||||||
|
|
||||||
|
Expected outcome: error and success flash messages are legible and visually distinct in both light and dark modes.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Run syntax check for `views/index.php`**
|
||||||
|
|
||||||
|
Run: `php -l views/index.php`
|
||||||
|
|
||||||
|
Expected: `No syntax errors detected in views/index.php`.
|
||||||
|
|
||||||
|
- [ ] **Step 9: Manual verification for `/home`**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- theme toggles correctly
|
||||||
|
- theme persists after reload
|
||||||
|
- cards/tables/modals remain readable in both themes
|
||||||
|
|
||||||
|
- [ ] **Step 10: Commit Task 3**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add views/index.php
|
||||||
|
git commit -m "feat: integrate dark mode support in home view"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: Integrate theme partial into vehicle detail view
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `views/vehicle.php`
|
||||||
|
- Test: `/vehicles/{id}`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add head include in `views/vehicle.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: after Tailwind/bootstrap includes and before `</head>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one head include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add body include in `views/vehicle.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: before page-local scripts and before `</body>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one body include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update page shell, vehicle card, and maintenance form in `views/vehicle.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- body/header/card backgrounds `bg-white/bg-gray-*` -> theme classes
|
||||||
|
- text grays on headings/body -> `theme-text` / `theme-text-muted`
|
||||||
|
- form inputs/borders -> `theme-input theme-border`
|
||||||
|
|
||||||
|
Expected outcome: top section and add-maintenance form are readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update dropdown menu surfaces in `views/vehicle.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- suggestions box `bg-white border-gray-300` -> `theme-surface theme-border`
|
||||||
|
- export menu panel `bg-white` -> `theme-surface`
|
||||||
|
- export menu links `hover:bg-gray-100` -> `hover:opacity-95`
|
||||||
|
- export menu link text grays -> `theme-text`
|
||||||
|
|
||||||
|
Expected outcome: dropdown menus remain readable and interactive in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Update maintenance history surfaces in `views/vehicle.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- history container `bg-white` -> `theme-surface`
|
||||||
|
- item cards `border-gray-200` -> `theme-border`
|
||||||
|
- item card hover `hover:border-blue-300` -> `hover:border-blue-500`
|
||||||
|
- item metadata text gray shades -> `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: maintenance history cards remain readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Update modal panel and input surfaces in `views/vehicle.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- edit modal panels `bg-white` -> `theme-surface`
|
||||||
|
- modal input `border-gray-300` -> `theme-input theme-border`
|
||||||
|
- modal close/cancel text grays -> `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: dropdowns, history cards, and modals remain readable and interactive in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run syntax check for `views/vehicle.php`**
|
||||||
|
|
||||||
|
Run: `php -l views/vehicle.php`
|
||||||
|
|
||||||
|
Expected: `No syntax errors detected in views/vehicle.php`.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Manual verification for `/vehicles/{id}`**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- theme toggles and persists
|
||||||
|
- forms/dropdowns/modals remain readable in both themes
|
||||||
|
- navigation to/from `/home` keeps cookie-based preference
|
||||||
|
|
||||||
|
- [ ] **Step 9: Commit Task 4**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add views/vehicle.php
|
||||||
|
git commit -m "feat: integrate dark mode support in vehicle detail view"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 5: Integrate theme partial into settings view
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `views/settings.php`
|
||||||
|
- Test: `/settings`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add head include in `views/settings.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: after Tailwind/bootstrap includes and before `</head>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one head include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add body include in `views/settings.php`**
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
Placement requirement: before local scripts (if any) and before `</body>`.
|
||||||
|
|
||||||
|
Expected outcome: exactly one body include in required position.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update page/header/card surfaces in `views/settings.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- body/header/cards `bg-white/bg-gray-*` -> `theme-page`/`theme-surface`/`theme-surface-alt`
|
||||||
|
- gray text classes -> `theme-text` / `theme-text-muted`
|
||||||
|
|
||||||
|
Expected outcome: page shell and cards remain readable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update list rows and password form surfaces in `views/settings.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- quick-task row hover/background and borders -> theme classes
|
||||||
|
- password input borders/backgrounds -> `theme-input theme-border`
|
||||||
|
|
||||||
|
Expected outcome: list rows and form fields are readable and distinguishable in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify action/footer contrast in `views/settings.php`**
|
||||||
|
|
||||||
|
Concrete updates:
|
||||||
|
- logout action danger colors may be adjusted if needed for dark-mode contrast while preserving danger semantics
|
||||||
|
- powered footer card `bg-white` -> `theme-surface`
|
||||||
|
- powered footer title/body grays -> `theme-text` / `theme-text-muted`
|
||||||
|
- GitHub icon/link muted grays -> `theme-text-muted` while preserving blue link emphasis
|
||||||
|
|
||||||
|
Expected outcome: call-to-action and footer text remain legible in both themes.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run syntax check for `views/settings.php`**
|
||||||
|
|
||||||
|
Run: `php -l views/settings.php`
|
||||||
|
|
||||||
|
Expected: `No syntax errors detected in views/settings.php`.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Manual verification for `/settings`**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- theme toggles and persists
|
||||||
|
- cards/list rows/inputs remain readable in both themes
|
||||||
|
- navigation to/from `/home` keeps cookie-based preference
|
||||||
|
|
||||||
|
- [ ] **Step 8: Commit Task 5**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add views/settings.php
|
||||||
|
git commit -m "feat: integrate dark mode support in settings view"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 6: Validate export exception and complete end-to-end checks
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify unchanged: `views/export.php`
|
||||||
|
- Verify behavior: `views/partials/theme.php`
|
||||||
|
- Verify integrated views: `views/login.php`, `views/setup.php`, `views/index.php`, `views/vehicle.php`, `views/settings.php`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Verify `views/export.php` has no theme include/toggle**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- no `$themeSection` include
|
||||||
|
- no `#themeToggle` markup
|
||||||
|
- no dark-mode bootstrap script and no `html.dark` application path in export template
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify cookie contract in devtools**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- toggle theme on any themed page
|
||||||
|
- inspect cookie attributes
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- cookie name `theme`
|
||||||
|
- value `dark` or `light`
|
||||||
|
- `Max-Age=31536000`
|
||||||
|
- `Path=/`
|
||||||
|
- `SameSite=Lax`
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify accessibility state updates on toggle**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- focus toggle via keyboard
|
||||||
|
- activate twice
|
||||||
|
|
||||||
|
Expected per click:
|
||||||
|
- icon swaps moon/sun
|
||||||
|
- `aria-label` and `title` update to next action
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify export is always light**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- set `theme=dark`
|
||||||
|
- open `/vehicles/{id}/export/html`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- export remains light styled
|
||||||
|
- toggle not present
|
||||||
|
- `<html>` does not receive `dark` class on export, even when `theme=dark`
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify toggle stays visible above modals/dropdowns**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- open each page with modal/dropdown support (`/home`, `/vehicles/{id}`)
|
||||||
|
- open add/edit modals and export/suggestions dropdowns
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- floating toggle remains visible and clickable above overlays
|
||||||
|
|
||||||
|
- [ ] **Step 5a: Apply layering remediation if Step 5 fails**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- if toggle is hidden or unclickable over overlays, adjust stacking order in `views/partials/theme.php`
|
||||||
|
|
||||||
|
Concrete remediation:
|
||||||
|
- increase `#themeToggle` z-index above conflicting overlay layer
|
||||||
|
- if conflict persists, reduce non-critical overlay z-index where safe without breaking modal usability
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- toggle remains visible/clickable while modals/dropdowns still function correctly
|
||||||
|
|
||||||
|
- [ ] **Step 6: Verify no-cookie fallback uses system preference**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- delete `theme` cookie
|
||||||
|
- load `/home`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- resolved theme follows current `prefers-color-scheme`
|
||||||
|
|
||||||
|
- [ ] **Step 7: Verify invalid-cookie fallback**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- set `theme=invalid`
|
||||||
|
- reload `/home`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- invalid cookie ignored
|
||||||
|
- resolved theme follows current system preference
|
||||||
|
|
||||||
|
- [ ] **Step 8: Verify no-JS fallback**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- disable JS in browser
|
||||||
|
- open `/login`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- light mode only
|
||||||
|
- no visible floating toggle
|
||||||
|
- login form remains usable
|
||||||
|
|
||||||
|
- [ ] **Step 9: Verify cookie-write-failure behavior**
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
- use a browser mode/policy that blocks cookie writes (or simulate via devtools/privacy settings)
|
||||||
|
- click toggle on `/home`
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- theme still switches immediately on the current page
|
||||||
|
- no error message is shown to the user
|
||||||
|
- persistence across reload is not required in this scenario
|
||||||
|
|
||||||
|
- [ ] **Step 10: Run final PHP syntax sweep**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php -l views/partials/theme.php && \
|
||||||
|
php -l views/login.php && \
|
||||||
|
php -l views/setup.php && \
|
||||||
|
php -l views/index.php && \
|
||||||
|
php -l views/vehicle.php && \
|
||||||
|
php -l views/settings.php
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all report `No syntax errors detected`.
|
||||||
|
|
||||||
|
- [ ] **Step 11: No additional commit for verification-only task**
|
||||||
|
|
||||||
|
Expected:
|
||||||
|
- Task 6 records verification results only.
|
||||||
|
- No new commit is created unless this task introduces actual file changes.
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# Dark Mode Support Design
|
||||||
|
|
||||||
|
Date: 2026-05-01
|
||||||
|
Project: Mainty
|
||||||
|
Status: Draft approved by user for implementation planning
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add dark mode support across all regular app views with a floating dark/light toggle in the top-right corner at all times (for normal JS-enabled operation). Theme should default to the system preference when no user preference exists, and user preference should persist in a cookie.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- Add theme resolution and apply logic for:
|
||||||
|
- `views/index.php`
|
||||||
|
- `views/vehicle.php`
|
||||||
|
- `views/settings.php`
|
||||||
|
- `views/login.php`
|
||||||
|
- `views/setup.php`
|
||||||
|
- Add a floating top-right theme toggle visible on all in-scope views.
|
||||||
|
- Persist user-selected theme in a cookie.
|
||||||
|
- Apply cookie preference on view load.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- No database persistence for theme preference.
|
||||||
|
- No account-level sync across devices.
|
||||||
|
- No changes to route/controller/model logic.
|
||||||
|
- No dark mode support for export/print page.
|
||||||
|
|
||||||
|
## Explicit Product Decisions
|
||||||
|
|
||||||
|
- Export page remains always light for print readability:
|
||||||
|
- `views/export.php` must ignore cookie and system theme.
|
||||||
|
- If JavaScript is disabled, app falls back to light theme.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
## 1) Shared Theme Partial
|
||||||
|
|
||||||
|
Create a reusable partial: `views/partials/theme.php`.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- Inject minimal theme bootstrap script in `<head>` to resolve theme and apply `dark` class on `<html>` as early as possible.
|
||||||
|
- Inject floating theme toggle markup near the start/end of `<body>`.
|
||||||
|
- Inject shared theme CSS variables and a concise set of utility mappings for common surfaces and text colors.
|
||||||
|
- Expose small JS helpers for:
|
||||||
|
- reading cookie,
|
||||||
|
- writing cookie,
|
||||||
|
- resolving effective theme,
|
||||||
|
- updating UI state/icon/labels,
|
||||||
|
- toggling theme on click.
|
||||||
|
|
||||||
|
Why:
|
||||||
|
|
||||||
|
- Keeps behavior consistent across all views.
|
||||||
|
- Avoids duplicating script/CSS/button logic in each template.
|
||||||
|
- Reduces maintenance cost for future theme adjustments.
|
||||||
|
|
||||||
|
Interface contract:
|
||||||
|
|
||||||
|
- The partial is included with an explicit section selector:
|
||||||
|
- `$themeSection = 'head'; include __DIR__ . '/partials/theme.php';`
|
||||||
|
- `$themeSection = 'body'; include __DIR__ . '/partials/theme.php';`
|
||||||
|
- `'head'` section outputs only bootstrap script + shared theme styles.
|
||||||
|
- `'body'` section outputs only floating toggle markup + behavior wiring script.
|
||||||
|
- Any other/missing section outputs nothing (safe no-op).
|
||||||
|
|
||||||
|
## 2) View Integration Points
|
||||||
|
|
||||||
|
Integrate the partial into each in-scope view.
|
||||||
|
|
||||||
|
- Include contract per themed view:
|
||||||
|
- In `<head>` (after Tailwind CDN script/link and before `</head>`): set `$themeSection = 'head'` and include `views/partials/theme.php`.
|
||||||
|
- In `<body>` (before page-local scripts and before `</body>`): set `$themeSection = 'body'` and include `views/partials/theme.php`.
|
||||||
|
- Partial integration must not require any page-specific IDs.
|
||||||
|
- Partial must be self-contained and safe to include on all in-scope views without per-view conditionals.
|
||||||
|
|
||||||
|
Each view keeps existing layout and structure; only color semantics are adjusted where required for dark compatibility.
|
||||||
|
|
||||||
|
## 3) Export Exception
|
||||||
|
|
||||||
|
`views/export.php` remains standalone light styling:
|
||||||
|
|
||||||
|
- No theme partial include.
|
||||||
|
- No floating toggle.
|
||||||
|
- No dark class application.
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
## Initial Load
|
||||||
|
|
||||||
|
1. Parse cookie `theme`.
|
||||||
|
2. If cookie is `dark` or `light`, use it.
|
||||||
|
3. Else query `window.matchMedia('(prefers-color-scheme: dark)')`.
|
||||||
|
4. Apply result by toggling `document.documentElement.classList` with `dark`.
|
||||||
|
5. Render page using applied theme classes/variables.
|
||||||
|
|
||||||
|
## User Toggle Interaction
|
||||||
|
|
||||||
|
1. User clicks floating toggle.
|
||||||
|
2. Determine next theme (`light` -> `dark`, `dark` -> `light`).
|
||||||
|
3. Update `dark` class on `<html>` immediately.
|
||||||
|
4. Attempt to write cookie `theme=<dark|light>` with long-lived expiration, `path=/`, `SameSite=Lax`.
|
||||||
|
5. Update toggle icon/accessible label/title to reflect new mode.
|
||||||
|
6. If cookie persistence fails (blocked by browser/privacy settings), keep the switched theme for the current document and do not show an error; next navigation/load falls back to normal resolution flow (system preference if no valid cookie).
|
||||||
|
|
||||||
|
## Subsequent Loads
|
||||||
|
|
||||||
|
- Cookie value takes precedence over system preference.
|
||||||
|
- Users with no valid cookie continue following system theme.
|
||||||
|
|
||||||
|
## Theme Styling Strategy
|
||||||
|
|
||||||
|
Use a hybrid of CSS variables + targeted Tailwind class updates.
|
||||||
|
|
||||||
|
- Keep existing spacing/layout/structure classes.
|
||||||
|
- Introduce semantic surface/text variable mappings for repeated patterns.
|
||||||
|
- Update key hardcoded light-only color usages in templates where needed:
|
||||||
|
- page backgrounds,
|
||||||
|
- headers/cards/panels,
|
||||||
|
- tables (head/body row hover),
|
||||||
|
- forms and inputs,
|
||||||
|
- modals/dropdowns,
|
||||||
|
- flash messages.
|
||||||
|
|
||||||
|
Acceptance scope boundary for theming edits:
|
||||||
|
|
||||||
|
- Only update color-related classes/styles needed for readability/contrast in dark mode.
|
||||||
|
- Do not change spacing, typography scale, layout structure, or route/form behavior.
|
||||||
|
- A view is considered complete when all listed surface types in that view remain readable and interactive in both light and dark modes.
|
||||||
|
|
||||||
|
Action colors (primary blue, danger red) remain recognizable but are adjusted for contrast in dark contexts.
|
||||||
|
|
||||||
|
## Component Requirements
|
||||||
|
|
||||||
|
## Floating Toggle Button
|
||||||
|
|
||||||
|
- Position: fixed top-right, always visible in viewport.
|
||||||
|
- Layering: `z-index: 50`, intended to remain visible above regular page content and app modals.
|
||||||
|
- Visual states:
|
||||||
|
- sun icon when dark mode is active (indicates switch to light),
|
||||||
|
- moon icon when light mode is active (indicates switch to dark).
|
||||||
|
- Accessibility:
|
||||||
|
- semantic `<button type="button">`,
|
||||||
|
- `aria-label` updates per state,
|
||||||
|
- visible focus ring,
|
||||||
|
- keyboard operable by default.
|
||||||
|
|
||||||
|
## Cookie Contract
|
||||||
|
|
||||||
|
- Name: `theme`
|
||||||
|
- Allowed values: `dark`, `light`
|
||||||
|
- Ignore any other value as invalid.
|
||||||
|
- Attributes:
|
||||||
|
- `path=/`
|
||||||
|
- `SameSite=Lax`
|
||||||
|
- expiration `max-age=31536000` (1 year)
|
||||||
|
|
||||||
|
## Error Handling and Edge Cases
|
||||||
|
|
||||||
|
- Invalid cookie value -> treat as unset, fallback to system preference.
|
||||||
|
- `matchMedia` unavailable (unlikely) -> fallback light.
|
||||||
|
- JS disabled -> light theme only, app remains functional, and floating toggle is not shown.
|
||||||
|
- Cookie write blocked/fails -> keep current-page visual switch only; persistence not guaranteed on next load.
|
||||||
|
- System theme changes after load:
|
||||||
|
- if cookie exists, keep explicit user preference,
|
||||||
|
- if cookie absent, follow resolved system preference on next navigation/load.
|
||||||
|
|
||||||
|
## Testing and Verification
|
||||||
|
|
||||||
|
Because no automated test suite is configured, verification is manual + syntax checks.
|
||||||
|
|
||||||
|
## Manual Functional Checks
|
||||||
|
|
||||||
|
- Open each in-scope view and verify toggle is visible at top-right:
|
||||||
|
- `/home`, vehicle details, `/settings`, `/login`, `/setup`.
|
||||||
|
- With no cookie set:
|
||||||
|
- verify first load follows system preference.
|
||||||
|
- Click toggle:
|
||||||
|
- verify immediate theme switch,
|
||||||
|
- verify cookie is written,
|
||||||
|
- verify choice persists across reload and cross-page navigation.
|
||||||
|
- Set an invalid cookie value manually:
|
||||||
|
- verify fallback to system preference.
|
||||||
|
- Open export page:
|
||||||
|
- verify always light and no toggle.
|
||||||
|
- Disable JS (or simulate no JS):
|
||||||
|
- verify app remains usable in light mode and toggle is not rendered.
|
||||||
|
- Simulate system-theme scenario with no cookie:
|
||||||
|
- verify resolved theme follows current system preference on load.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
- `views/partials/theme.php` exists and is included in all in-scope views only.
|
||||||
|
- Floating top-right toggle appears on all in-scope views and is keyboard accessible.
|
||||||
|
- Theme resolution order is enforced: valid cookie first, otherwise system preference, otherwise light fallback.
|
||||||
|
- Clicking toggle updates theme immediately and attempts cookie persistence.
|
||||||
|
- Invalid cookie values are ignored safely.
|
||||||
|
- Export page remains always light and has no toggle.
|
||||||
|
- `php -l` passes for each changed PHP file.
|
||||||
|
|
||||||
|
## Syntax Checks
|
||||||
|
|
||||||
|
Run `php -l` for each changed PHP file:
|
||||||
|
|
||||||
|
- `views/partials/theme.php`
|
||||||
|
- each modified view file in scope
|
||||||
|
|
||||||
|
## Risks and Mitigations
|
||||||
|
|
||||||
|
- Risk: visual regressions from many color classes.
|
||||||
|
- Mitigation: limit edits to color semantics; keep structure/layout intact.
|
||||||
|
- Risk: flash of incorrect theme.
|
||||||
|
- Mitigation: apply theme class in early `<head>` script before body render.
|
||||||
|
- Risk: inconsistent behavior across pages.
|
||||||
|
- Mitigation: centralize logic in shared partial.
|
||||||
|
|
||||||
|
## Implementation Boundaries
|
||||||
|
|
||||||
|
- No refactors unrelated to theme support.
|
||||||
|
- Keep MVC boundaries untouched (view-only changes).
|
||||||
|
- Keep existing UX flows, redirects, and auth/setup guards unchanged.
|
||||||
@@ -2,6 +2,9 @@ RewriteEngine On
|
|||||||
#if this is in a folder, edit this. example.com/mainty/ would be /mainty/
|
#if this is in a folder, edit this. example.com/mainty/ would be /mainty/
|
||||||
RewriteBase /
|
RewriteBase /
|
||||||
|
|
||||||
|
# Block access to data directory
|
||||||
|
RewriteRule ^data/ - [F,L]
|
||||||
|
|
||||||
# Redirect to HTTPS (optional, uncomment if needed)
|
# Redirect to HTTPS (optional, uncomment if needed)
|
||||||
# RewriteCond %{HTTPS} off
|
# RewriteCond %{HTTPS} off
|
||||||
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||||
|
|||||||
+51
-48
@@ -6,14 +6,15 @@
|
|||||||
<title><?php echo APP_NAME; ?> - Vehicles</title>
|
<title><?php echo APP_NAME; ?> - Vehicles</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen">
|
<body class="theme-page min-h-screen">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="bg-white shadow-sm">
|
<header class="theme-surface shadow-sm">
|
||||||
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||||
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
|
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
|
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-95">
|
||||||
<i class="bi bi-gear-fill text-2xl"></i>
|
<i class="bi bi-gear-fill text-2xl"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,28 +23,28 @@
|
|||||||
|
|
||||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success'])): ?>
|
<?php if (isset($_SESSION['success'])): ?>
|
||||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Header with view toggle and add button -->
|
<!-- Header with view toggle and add button -->
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<h2 class="text-xl font-semibold text-gray-700">My Vehicles</h2>
|
<h2 class="text-xl font-semibold theme-text">My Vehicles</h2>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="flex bg-white rounded-lg shadow-sm">
|
<div class="flex theme-surface rounded-lg shadow-sm">
|
||||||
<a href="<?php echo url('/home?view=grid'); ?>"
|
<a href="<?php echo url('/home?view=grid'); ?>"
|
||||||
class="px-3 py-2 <?php echo $viewMode === 'grid' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:bg-gray-100'; ?> rounded-l-lg transition">
|
class="px-3 py-2 <?php echo $viewMode === 'grid' ? 'bg-blue-600 text-white' : 'theme-text-muted hover:opacity-95'; ?> rounded-l-lg transition">
|
||||||
<i class="bi bi-grid-3x3-gap-fill"></i>
|
<i class="bi bi-grid-3x3-gap-fill"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo url('/home?view=list'); ?>"
|
<a href="<?php echo url('/home?view=list'); ?>"
|
||||||
class="px-3 py-2 <?php echo $viewMode === 'list' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:bg-gray-100'; ?> rounded-r-lg transition">
|
class="px-3 py-2 <?php echo $viewMode === 'list' ? 'bg-blue-600 text-white' : 'theme-text-muted hover:opacity-95'; ?> rounded-r-lg transition">
|
||||||
<i class="bi bi-list-ul"></i>
|
<i class="bi bi-list-ul"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,10 +58,10 @@
|
|||||||
|
|
||||||
<!-- Vehicles Display -->
|
<!-- Vehicles Display -->
|
||||||
<?php if (empty($vehicles)): ?>
|
<?php if (empty($vehicles)): ?>
|
||||||
<div class="bg-white rounded-lg shadow-sm p-12 text-center">
|
<div class="theme-surface rounded-lg shadow-sm p-12 text-center">
|
||||||
<i class="bi bi-car-front text-6xl text-gray-300 mb-4"></i>
|
<i class="bi bi-car-front text-6xl theme-text-muted mb-4"></i>
|
||||||
<h3 class="text-xl font-semibold text-gray-700 mb-2">No vehicles yet</h3>
|
<h3 class="text-xl font-semibold theme-text mb-2">No vehicles yet</h3>
|
||||||
<p class="text-gray-500 mb-4">Get started by adding your first vehicle</p>
|
<p class="theme-text-muted mb-4">Get started by adding your first vehicle</p>
|
||||||
<button onclick="showAddVehicleModal()"
|
<button onclick="showAddVehicleModal()"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
||||||
Add Your First Vehicle
|
Add Your First Vehicle
|
||||||
@@ -71,15 +72,15 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<?php foreach ($vehicles as $vehicle): ?>
|
<?php foreach ($vehicles as $vehicle): ?>
|
||||||
<a href="<?php echo url('/vehicles/' . $vehicle['id']); ?>"
|
<a href="<?php echo url('/vehicles/' . $vehicle['id']); ?>"
|
||||||
class="bg-white rounded-lg shadow-sm hover:shadow-md transition p-6 block">
|
class="theme-surface rounded-lg shadow-sm hover:shadow-md transition p-6 block">
|
||||||
<div class="flex items-start justify-between mb-3">
|
<div class="flex items-start justify-between mb-3">
|
||||||
<div class="bg-blue-100 p-3 rounded-lg">
|
<div class="bg-blue-100 p-3 rounded-lg">
|
||||||
<i class="bi bi-car-front-fill text-blue-600 text-2xl"></i>
|
<i class="bi bi-car-front-fill text-blue-600 text-2xl"></i>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-500"><?php echo $vehicle['maintenance_count']; ?> records</span>
|
<span class="text-sm theme-text-muted"><?php echo $vehicle['maintenance_count']; ?> records</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h3>
|
<h3 class="text-lg font-semibold theme-text mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h3>
|
||||||
<div class="space-y-1 text-sm text-gray-600">
|
<div class="space-y-1 text-sm theme-text">
|
||||||
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
|
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
|
||||||
<p><?php echo implode(' ', array_filter([$vehicle['year'], $vehicle['make'], $vehicle['model']])); ?></p>
|
<p><?php echo implode(' ', array_filter([$vehicle['year'], $vehicle['make'], $vehicle['model']])); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -87,47 +88,47 @@
|
|||||||
<p><i class="bi bi-credit-card-2-front"></i> <?php echo htmlspecialchars($vehicle['license_plate']); ?></p>
|
<p><i class="bi bi-credit-card-2-front"></i> <?php echo htmlspecialchars($vehicle['license_plate']); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ($vehicle['last_maintenance_date']): ?>
|
<?php if ($vehicle['last_maintenance_date']): ?>
|
||||||
<p class="text-xs text-gray-500 mt-2">Last service: <?php echo date('M d, Y', strtotime($vehicle['last_maintenance_date'])); ?></p>
|
<p class="text-xs theme-text-muted mt-2">Last service: <?php echo date('M d, Y', strtotime($vehicle['last_maintenance_date'])); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
<div class="theme-surface rounded-lg shadow-sm overflow-hidden">
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead class="bg-gray-50 border-b">
|
<thead class="theme-surface-alt theme-border border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Vehicle</th>
|
<th class="px-6 py-3 text-left text-xs font-medium theme-text-muted uppercase tracking-wider">Vehicle</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th>
|
<th class="px-6 py-3 text-left text-xs font-medium theme-text-muted uppercase tracking-wider">Details</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">License Plate</th>
|
<th class="px-6 py-3 text-left text-xs font-medium theme-text-muted uppercase tracking-wider">License Plate</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Records</th>
|
<th class="px-6 py-3 text-left text-xs font-medium theme-text-muted uppercase tracking-wider">Records</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Service</th>
|
<th class="px-6 py-3 text-left text-xs font-medium theme-text-muted uppercase tracking-wider">Last Service</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody class="theme-row-dividers">
|
||||||
<?php foreach ($vehicles as $vehicle): ?>
|
<?php foreach ($vehicles as $vehicle): ?>
|
||||||
<tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='<?php echo url('/vehicles/' . $vehicle['id']); ?>'">
|
<tr class="theme-surface-alt hover:opacity-95 cursor-pointer" onclick="window.location='<?php echo url('/vehicles/' . $vehicle['id']); ?>'">
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="bg-blue-100 p-2 rounded">
|
<div class="bg-blue-100 p-2 rounded">
|
||||||
<i class="bi bi-car-front-fill text-blue-600"></i>
|
<i class="bi bi-car-front-fill text-blue-600"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<div class="text-sm font-medium text-gray-900"><?php echo htmlspecialchars($vehicle['name']); ?></div>
|
<div class="text-sm font-medium theme-text"><?php echo htmlspecialchars($vehicle['name']); ?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
<td class="px-6 py-4 whitespace-nowrap text-sm theme-text">
|
||||||
<?php echo implode(' ', array_filter([$vehicle['year'], $vehicle['make'], $vehicle['model']])) ?: '-'; ?>
|
<?php echo implode(' ', array_filter([$vehicle['year'], $vehicle['make'], $vehicle['model']])) ?: '-'; ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
<td class="px-6 py-4 whitespace-nowrap text-sm theme-text">
|
||||||
<?php echo htmlspecialchars($vehicle['license_plate']) ?: '-'; ?>
|
<?php echo htmlspecialchars($vehicle['license_plate']) ?: '-'; ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
<td class="px-6 py-4 whitespace-nowrap text-sm theme-text">
|
||||||
<?php echo $vehicle['maintenance_count']; ?>
|
<?php echo $vehicle['maintenance_count']; ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
<td class="px-6 py-4 whitespace-nowrap text-sm theme-text">
|
||||||
<?php echo $vehicle['last_maintenance_date'] ? date('M d, Y', strtotime($vehicle['last_maintenance_date'])) : '-'; ?>
|
<?php echo $vehicle['last_maintenance_date'] ? date('M d, Y', strtotime($vehicle['last_maintenance_date'])) : '-'; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -141,51 +142,51 @@
|
|||||||
|
|
||||||
<!-- Add Vehicle Modal -->
|
<!-- Add Vehicle Modal -->
|
||||||
<div id="addVehicleModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div id="addVehicleModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
<div class="theme-surface rounded-lg p-6 w-full max-w-md">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-xl font-semibold">Add New Vehicle</h3>
|
<h3 class="text-xl font-semibold">Add New Vehicle</h3>
|
||||||
<button onclick="hideAddVehicleModal()" class="text-gray-500 hover:text-gray-700">
|
<button onclick="hideAddVehicleModal()" class="theme-text-muted hover:opacity-80">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="<?php echo url('/vehicles/add'); ?>">
|
<form method="POST" action="<?php echo url('/vehicles/add'); ?>">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Name *</label>
|
||||||
<input type="text" name="name" required
|
<input type="text" name="name" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Year</label>
|
<label class="block text-sm font-medium theme-text mb-1">Year</label>
|
||||||
<input type="text" name="year"
|
<input type="text" name="year"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Color</label>
|
<label class="block text-sm font-medium theme-text mb-1">Color</label>
|
||||||
<input type="text" name="color"
|
<input type="text" name="color"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Make</label>
|
<label class="block text-sm font-medium theme-text mb-1">Make</label>
|
||||||
<input type="text" name="make"
|
<input type="text" name="make"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Model</label>
|
<label class="block text-sm font-medium theme-text mb-1">Model</label>
|
||||||
<input type="text" name="model"
|
<input type="text" name="model"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">License Plate</label>
|
<label class="block text-sm font-medium theme-text mb-1">License Plate</label>
|
||||||
<input type="text" name="license_plate"
|
<input type="text" name="license_plate"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-border theme-input rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-3 mt-6">
|
<div class="flex space-x-3 mt-6">
|
||||||
<button type="button" onclick="hideAddVehicleModal()"
|
<button type="button" onclick="hideAddVehicleModal()"
|
||||||
class="flex-1 px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition">
|
class="flex-1 px-4 py-2 border theme-border rounded-md theme-text-muted hover:opacity-95 transition">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@@ -197,6 +198,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function showAddVehicleModal() {
|
function showAddVehicleModal() {
|
||||||
document.getElementById('addVehicleModal').classList.remove('hidden');
|
document.getElementById('addVehicleModal').classList.remove('hidden');
|
||||||
|
|||||||
+9
-7
@@ -6,28 +6,29 @@
|
|||||||
<title><?php echo APP_NAME; ?> - Login</title>
|
<title><?php echo APP_NAME; ?> - Login</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
|
<body class="theme-page min-h-screen flex items-center justify-center">
|
||||||
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
|
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
|
||||||
<h1 class="text-3xl font-bold text-gray-800 mb-6 text-center"><?php echo APP_NAME; ?></h1>
|
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?></h1>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success'])): ?>
|
<?php if (isset($_SESSION['success'])): ?>
|
||||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
|
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
<label for="password" class="block text-sm font-medium theme-text mb-1">Password</label>
|
||||||
<input type="password" id="password" name="password" required autofocus
|
<input type="password" id="password" name="password" required autofocus
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
placeholder="Enter your password">
|
placeholder="Enter your password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -37,5 +38,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
$themeSection = $themeSection ?? null;
|
||||||
|
|
||||||
|
if ($themeSection === 'head'):
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
function readThemeCookie() {
|
||||||
|
var cookieParts = document.cookie ? document.cookie.split(';') : [];
|
||||||
|
|
||||||
|
for (var i = 0; i < cookieParts.length; i++) {
|
||||||
|
var part = cookieParts[i].trim();
|
||||||
|
if (part.indexOf('theme=') === 0) {
|
||||||
|
var value = decodeURIComponent(part.substring(6));
|
||||||
|
if (value === 'dark' || value === 'light') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTheme() {
|
||||||
|
var cookieTheme = readThemeCookie();
|
||||||
|
if (cookieTheme) {
|
||||||
|
return cookieTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedTheme = resolveTheme();
|
||||||
|
var html = document.documentElement;
|
||||||
|
|
||||||
|
html.classList.toggle('dark', resolvedTheme === 'dark');
|
||||||
|
html.setAttribute('data-theme', resolvedTheme);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--theme-bg: #f3f4f6;
|
||||||
|
--theme-surface: #ffffff;
|
||||||
|
--theme-surface-alt: #f9fafb;
|
||||||
|
--theme-text: #111827;
|
||||||
|
--theme-text-muted: #6b7280;
|
||||||
|
--theme-border: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
--theme-bg: #111827;
|
||||||
|
--theme-surface: #1f2937;
|
||||||
|
--theme-surface-alt: #111827;
|
||||||
|
--theme-text: #f3f4f6;
|
||||||
|
--theme-text-muted: #9ca3af;
|
||||||
|
--theme-border: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-page {
|
||||||
|
background-color: var(--theme-bg);
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-surface {
|
||||||
|
background-color: var(--theme-surface);
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-surface-alt {
|
||||||
|
background-color: var(--theme-surface-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-text {
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-text-muted {
|
||||||
|
color: var(--theme-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-border {
|
||||||
|
border-color: var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-row-dividers > tr {
|
||||||
|
border-bottom: 1px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-row-dividers > tr:last-child {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-input {
|
||||||
|
background-color: var(--theme-surface);
|
||||||
|
border-color: var(--theme-border);
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-input::placeholder {
|
||||||
|
color: var(--theme-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#themeToggle {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(1rem + env(safe-area-inset-top));
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#themeToggle {
|
||||||
|
top: calc(4.75rem + env(safe-area-inset-top));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#themeToggle:focus-visible {
|
||||||
|
outline: 3px solid #2563eb;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
|
||||||
|
if ($themeSection === 'body'):
|
||||||
|
?>
|
||||||
|
<button
|
||||||
|
id="themeToggle"
|
||||||
|
type="button"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white p-3 rounded-full shadow-md transition"
|
||||||
|
aria-label="Switch to dark mode"
|
||||||
|
title="Switch to dark mode"
|
||||||
|
>
|
||||||
|
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<style>
|
||||||
|
#themeToggle {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var html = document.documentElement;
|
||||||
|
var toggleButton = document.getElementById('themeToggle');
|
||||||
|
var toggleIcon = document.getElementById('themeToggleIcon');
|
||||||
|
|
||||||
|
if (!toggleButton || !toggleIcon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readThemeCookie() {
|
||||||
|
var cookieParts = document.cookie ? document.cookie.split(';') : [];
|
||||||
|
|
||||||
|
for (var i = 0; i < cookieParts.length; i++) {
|
||||||
|
var part = cookieParts[i].trim();
|
||||||
|
if (part.indexOf('theme=') === 0) {
|
||||||
|
var value = decodeURIComponent(part.substring(6));
|
||||||
|
if (value === 'dark' || value === 'light') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeThemeCookie(theme) {
|
||||||
|
document.cookie = 'theme=' + encodeURIComponent(theme) + '; max-age=31536000; path=/; SameSite=Lax';
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
var validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||||
|
html.classList.toggle('dark', validTheme === 'dark');
|
||||||
|
html.setAttribute('data-theme', validTheme);
|
||||||
|
return validTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncToggle(theme) {
|
||||||
|
var isDark = theme === 'dark';
|
||||||
|
var label = isDark ? 'Switch to light mode' : 'Switch to dark mode';
|
||||||
|
var iconClass = isDark ? 'bi-sun-fill' : 'bi-moon-stars-fill';
|
||||||
|
|
||||||
|
toggleButton.setAttribute('aria-label', label);
|
||||||
|
toggleButton.setAttribute('title', label);
|
||||||
|
toggleButton.setAttribute('aria-pressed', isDark ? 'true' : 'false');
|
||||||
|
toggleIcon.className = 'bi ' + iconClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialTheme;
|
||||||
|
if (html.classList.contains('dark')) {
|
||||||
|
initialTheme = 'dark';
|
||||||
|
} else {
|
||||||
|
initialTheme = readThemeCookie() || 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTheme = applyTheme(initialTheme);
|
||||||
|
syncToggle(currentTheme);
|
||||||
|
|
||||||
|
toggleButton.addEventListener('click', function () {
|
||||||
|
var nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||||
|
|
||||||
|
currentTheme = applyTheme(nextTheme);
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeThemeCookie(currentTheme);
|
||||||
|
} catch (error) {
|
||||||
|
// Visual switch remains active if cookie storage is restricted.
|
||||||
|
}
|
||||||
|
|
||||||
|
syncToggle(currentTheme);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
+44
-32
@@ -6,14 +6,15 @@
|
|||||||
<title><?php echo APP_NAME; ?> - Settings</title>
|
<title><?php echo APP_NAME; ?> - Settings</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen">
|
<body class="theme-page min-h-screen">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="bg-white shadow-sm">
|
<header class="theme-surface shadow-sm">
|
||||||
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||||
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
|
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
|
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-90">
|
||||||
<i class="bi bi-gear-fill text-2xl"></i>
|
<i class="bi bi-gear-fill text-2xl"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,37 +24,43 @@
|
|||||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="<?php echo url('/home'); ?>" class="text-blue-600 hover:text-blue-800">← Back to Vehicles</a>
|
<a href="<?php echo url('/home'); ?>" class="font-medium transition hover:opacity-90" style="color: #2563eb; color: color-mix(in srgb, #3b82f6 72%, var(--theme-text));">← Back to Vehicles</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success'])): ?>
|
<?php if (isset($_SESSION['success'])): ?>
|
||||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<h2 class="text-2xl font-bold text-gray-800 mb-6">Settings</h2>
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-2xl font-bold theme-text">Settings</h2>
|
||||||
|
<a href="<?php echo url('/logout'); ?>"
|
||||||
|
class="inline-flex items-center bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition">
|
||||||
|
<i class="bi bi-box-arrow-right mr-2"></i> Logout
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<!-- Quick Tasks Section -->
|
<!-- Quick Tasks Section -->
|
||||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
<div class="theme-surface rounded-lg shadow-sm p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
|
<h3 class="text-lg font-semibold theme-text mb-4 flex items-center">
|
||||||
<i class="bi bi-lightning-charge-fill text-yellow-500 mr-2"></i>
|
<i class="bi bi-lightning-charge-fill text-yellow-500 mr-2"></i>
|
||||||
Quick Tasks
|
Quick Tasks
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 mb-4">Predefined maintenance items for quick selection when adding records.</p>
|
<p class="text-sm theme-text-muted mb-4">Predefined maintenance items for quick selection when adding records.</p>
|
||||||
|
|
||||||
<!-- Add Quick Task Form -->
|
<!-- Add Quick Task Form -->
|
||||||
<form method="POST" action="<?php echo url('/settings/quick-tasks/add'); ?>" class="mb-4">
|
<form method="POST" action="<?php echo url('/settings/quick-tasks/add'); ?>" class="mb-4">
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<input type="text" name="name" required placeholder="New task name"
|
<input type="text" name="name" required placeholder="New task name"
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="flex-1 px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition">
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition">
|
||||||
<i class="bi bi-plus-lg"></i> Add
|
<i class="bi bi-plus-lg"></i> Add
|
||||||
@@ -64,11 +71,11 @@
|
|||||||
<!-- Quick Tasks List -->
|
<!-- Quick Tasks List -->
|
||||||
<div class="space-y-2 max-h-96 overflow-y-auto">
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||||
<?php foreach ($quickTasks as $task): ?>
|
<?php foreach ($quickTasks as $task): ?>
|
||||||
<div class="flex items-center justify-between p-2 hover:bg-gray-50 rounded border border-gray-200">
|
<div class="flex items-center justify-between p-2 theme-surface-alt hover:opacity-95 rounded border theme-border">
|
||||||
<span class="text-gray-700"><?php echo htmlspecialchars($task['name']); ?></span>
|
<span class="theme-text"><?php echo htmlspecialchars($task['name']); ?></span>
|
||||||
<form method="POST" action="<?php echo url('/settings/quick-tasks/' . $task['id'] . '/delete'); ?>"
|
<form method="POST" action="<?php echo url('/settings/quick-tasks/' . $task['id'] . '/delete'); ?>"
|
||||||
onsubmit="return confirm('Are you sure you want to delete this quick task?')" class="inline">
|
onsubmit="return confirm('Are you sure you want to delete this quick task?')" class="inline">
|
||||||
<button type="submit" class="text-red-600 hover:text-red-800">
|
<button type="submit" class="transition hover:opacity-85" style="color: #dc2626; color: color-mix(in srgb, #ef4444 78%, var(--theme-text));">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -78,31 +85,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Change Password Section -->
|
<!-- Change Password Section -->
|
||||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
<div class="theme-surface rounded-lg shadow-sm p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
|
<h3 class="text-lg font-semibold theme-text mb-4 flex items-center">
|
||||||
<i class="bi bi-key-fill text-blue-500 mr-2"></i>
|
<i class="bi bi-key-fill text-blue-500 mr-2"></i>
|
||||||
Change Password
|
Change Password
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-600 mb-4">Update your login password.</p>
|
<p class="text-sm theme-text-muted mb-4">Update your login password.</p>
|
||||||
|
|
||||||
<form method="POST" action="<?php echo url('/settings/password'); ?>" class="space-y-4">
|
<form method="POST" action="<?php echo url('/settings/password'); ?>" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Current Password</label>
|
<label class="block text-sm font-medium theme-text mb-1">Current Password</label>
|
||||||
<input type="password" name="current_password" required
|
<input type="password" name="current_password" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">New Password</label>
|
<label class="block text-sm font-medium theme-text mb-1">New Password</label>
|
||||||
<input type="password" name="new_password" required
|
<input type="password" name="new_password" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
placeholder="At least 6 characters">
|
placeholder="At least 6 characters">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Confirm New Password</label>
|
<label class="block text-sm font-medium theme-text mb-1">Confirm New Password</label>
|
||||||
<input type="password" name="confirm_password" required
|
<input type="password" name="confirm_password" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@@ -113,17 +120,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Logout Section -->
|
<!-- Powered by Mainty Section -->
|
||||||
<div class="mt-6 bg-white rounded-lg shadow-sm p-6">
|
<div class="mt-6 theme-surface rounded-lg shadow-sm p-6 text-center">
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
|
<h3 class="text-lg font-semibold theme-text mb-2 flex items-center justify-center">
|
||||||
<i class="bi bi-box-arrow-right text-red-500 mr-2"></i>
|
<i class="bi bi-github theme-text-muted mr-2"></i>
|
||||||
Account
|
Powered by Mainty, a project by Michael Staake and the community.
|
||||||
</h3>
|
</h3>
|
||||||
<a href="<?php echo url('/logout'); ?>"
|
<p class="text-sm theme-text-muted mb-3">
|
||||||
class="inline-block bg-red-100 hover:bg-red-200 text-red-700 px-6 py-2 rounded-md transition">
|
Get the latest version, learn more, or report issues on the official project GitHub.
|
||||||
<i class="bi bi-box-arrow-right"></i> Logout
|
</p>
|
||||||
|
<a href="https://github.com/michaelstaake/mainty" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center font-medium transition hover:opacity-90" style="color: #2563eb; color: color-mix(in srgb, #3b82f6 72%, var(--theme-text));">
|
||||||
|
<i class="bi bi-box-arrow-up-right theme-text-muted mr-1"></i>
|
||||||
|
github.com/michaelstaake/mainty
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+13
-11
@@ -6,19 +6,20 @@
|
|||||||
<title><?php echo APP_NAME; ?> - Setup</title>
|
<title><?php echo APP_NAME; ?> - Setup</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
|
<body class="theme-page min-h-screen flex items-center justify-center">
|
||||||
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
|
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
|
||||||
<h1 class="text-3xl font-bold text-gray-800 mb-6 text-center"><?php echo APP_NAME; ?> Setup</h1>
|
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?> Setup</h1>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded">
|
<div class="mb-6 p-4 border rounded" style="background-color: #eff6ff; background-color: color-mix(in srgb, #3b82f6 12%, var(--theme-surface)); border-color: #bfdbfe; border-color: color-mix(in srgb, #3b82f6 35%, var(--theme-border)); color: #1e3a8a; color: var(--theme-text);">
|
||||||
<h2 class="font-semibold text-blue-900 mb-2">System Requirements</h2>
|
<h2 class="font-semibold mb-2">System Requirements</h2>
|
||||||
<ul class="space-y-1 text-sm">
|
<ul class="space-y-1 text-sm">
|
||||||
<li class="flex items-center">
|
<li class="flex items-center">
|
||||||
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
|
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
|
||||||
@@ -47,23 +48,23 @@
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<?php if (!$canSetup): ?>
|
<?php if (!$canSetup): ?>
|
||||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-800 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fef9c3; background-color: color-mix(in srgb, #eab308 16%, var(--theme-surface)); border-color: #facc15; border-color: color-mix(in srgb, #eab308 38%, var(--theme-border)); color: #854d0e; color: var(--theme-text);">
|
||||||
<p class="font-semibold">System requirements not met!</p>
|
<p class="font-semibold">System requirements not met!</p>
|
||||||
<p class="text-sm mt-1">Please ensure PHP 8.0+ and SQLite extension are available.</p>
|
<p class="text-sm mt-1">Please ensure PHP 8.0+ and SQLite extension are available.</p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
|
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Create Password</label>
|
<label for="password" class="block text-sm font-medium theme-text-muted mb-1">Create Password</label>
|
||||||
<input type="password" id="password" name="password" required
|
<input type="password" id="password" name="password" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
placeholder="Enter password (min 6 characters)">
|
placeholder="Enter password (min 6 characters)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="confirm_password" class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label>
|
<label for="confirm_password" class="block text-sm font-medium theme-text-muted mb-1">Confirm Password</label>
|
||||||
<input type="password" id="confirm_password" name="confirm_password" required
|
<input type="password" id="confirm_password" name="confirm_password" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="w-full px-3 py-2 border border-gray-300 theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
placeholder="Confirm password">
|
placeholder="Confirm password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,5 +75,6 @@
|
|||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+76
-73
@@ -6,14 +6,15 @@
|
|||||||
<title><?php echo APP_NAME; ?> - <?php echo htmlspecialchars($vehicle['name']); ?></title>
|
<title><?php echo APP_NAME; ?> - <?php echo htmlspecialchars($vehicle['name']); ?></title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 min-h-screen">
|
<body class="theme-page min-h-screen">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="bg-white shadow-sm">
|
<header class="theme-surface shadow-sm">
|
||||||
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
<div class="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||||
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
|
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
|
<a href="<?php echo url('/settings'); ?>" class="theme-text-muted hover:opacity-90">
|
||||||
<i class="bi bi-gear-fill text-2xl"></i>
|
<i class="bi bi-gear-fill text-2xl"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,31 +24,31 @@
|
|||||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="<?php echo url('/home'); ?>" class="text-blue-600 hover:text-blue-800">← Back to Vehicles</a>
|
<a href="<?php echo url('/home'); ?>" class="theme-text-muted hover:opacity-90">← Back to Vehicles</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['error'])): ?>
|
<?php if (isset($_SESSION['error'])): ?>
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #fee2e2; background-color: color-mix(in srgb, #ef4444 14%, var(--theme-surface)); border-color: #fca5a5; border-color: color-mix(in srgb, #ef4444 40%, var(--theme-border)); color: #7f1d1d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($_SESSION['success'])): ?>
|
<?php if (isset($_SESSION['success'])): ?>
|
||||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
<div class="border px-4 py-3 rounded mb-4" style="background-color: #dcfce7; background-color: color-mix(in srgb, #22c55e 14%, var(--theme-surface)); border-color: #86efac; border-color: color-mix(in srgb, #22c55e 40%, var(--theme-border)); color: #14532d; color: var(--theme-text);">
|
||||||
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Vehicle Info Card -->
|
<!-- Vehicle Info Card -->
|
||||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
<div class="theme-surface rounded-lg shadow-sm p-6 mb-6">
|
||||||
<div class="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
|
<div class="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
|
||||||
<div class="flex flex-col sm:flex-row items-start gap-4">
|
<div class="flex flex-col sm:flex-row items-start gap-4">
|
||||||
<div class="bg-blue-100 p-4 rounded-lg">
|
<div class="bg-blue-100 p-4 rounded-lg">
|
||||||
<i class="bi bi-car-front-fill text-blue-600 text-3xl"></i>
|
<i class="bi bi-car-front-fill text-blue-600 text-3xl"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-bold text-gray-800 mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h2>
|
<h2 class="text-2xl font-bold theme-text mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h2>
|
||||||
<div class="space-y-1 text-gray-600">
|
<div class="space-y-1 theme-text-muted">
|
||||||
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
|
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
|
||||||
<p class="flex items-center">
|
<p class="flex items-center">
|
||||||
<i class="bi bi-info-circle mr-2"></i>
|
<i class="bi bi-info-circle mr-2"></i>
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<button onclick="showEditVehicleModal()"
|
<button onclick="showEditVehicleModal()"
|
||||||
class="px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition">
|
class="px-4 py-2 theme-surface theme-text border theme-border hover:opacity-95 rounded-lg transition">
|
||||||
<i class="bi bi-pencil"></i> <span class="hidden sm:inline">Edit</span>
|
<i class="bi bi-pencil"></i> <span class="hidden sm:inline">Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -79,13 +80,13 @@
|
|||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition">
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition">
|
||||||
<i class="bi bi-download"></i> <span class="hidden sm:inline">Export</span>
|
<i class="bi bi-download"></i> <span class="hidden sm:inline">Export</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="exportMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-10">
|
<div id="exportMenu" class="hidden absolute right-0 mt-2 w-48 theme-surface rounded-lg shadow-lg z-10">
|
||||||
<a href="<?php echo url('/vehicles/' . $vehicle['id'] . '/export/json'); ?>"
|
<a href="<?php echo url('/vehicles/' . $vehicle['id'] . '/export/json'); ?>"
|
||||||
class="block px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-t-lg">
|
class="block px-4 py-2 theme-text hover:opacity-95 rounded-t-lg">
|
||||||
<i class="bi bi-filetype-json"></i> Export as JSON
|
<i class="bi bi-filetype-json"></i> Export as JSON
|
||||||
</a>
|
</a>
|
||||||
<a href="<?php echo url('/vehicles/' . $vehicle['id'] . '/export/html'); ?>"
|
<a href="<?php echo url('/vehicles/' . $vehicle['id'] . '/export/html'); ?>"
|
||||||
class="block px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-b-lg">
|
class="block px-4 py-2 theme-text hover:opacity-95 rounded-b-lg">
|
||||||
<i class="bi bi-filetype-html"></i> Export as HTML
|
<i class="bi bi-filetype-html"></i> Export as HTML
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,47 +100,47 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Maintenance Item Card -->
|
<!-- Add Maintenance Item Card -->
|
||||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
<div class="theme-surface rounded-lg shadow-sm p-6 mb-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">Add Maintenance Record</h3>
|
<h3 class="text-lg font-semibold theme-text mb-4">Add Maintenance Record</h3>
|
||||||
<form method="POST" action="<?php echo url('/maintenance/add'); ?>">
|
<form method="POST" action="<?php echo url('/maintenance/add'); ?>">
|
||||||
<input type="hidden" name="vehicle_id" value="<?php echo $vehicle['id']; ?>">
|
<input type="hidden" name="vehicle_id" value="<?php echo $vehicle['id']; ?>">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Maintenance Name *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Maintenance Name *</label>
|
||||||
<input type="text" name="name" id="maintenanceName" required autocomplete="off"
|
<input type="text" name="name" id="maintenanceName" required autocomplete="off"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
<div id="suggestions" class="hidden absolute z-10 bg-white border border-gray-300 rounded-md shadow-lg mt-1 max-h-48 overflow-y-auto"></div>
|
<div id="suggestions" class="hidden absolute z-10 theme-surface border theme-border rounded-md shadow-lg mt-1 max-h-48 overflow-y-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Date *</label>
|
||||||
<input type="date" name="date" required value="<?php echo date('Y-m-d'); ?>"
|
<input type="date" name="date" required value="<?php echo date('Y-m-d'); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Mileage *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Mileage *</label>
|
||||||
<input type="number" name="mileage" required
|
<input type="number" name="mileage" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Cost</label>
|
<label class="block text-sm font-medium theme-text mb-1">Cost</label>
|
||||||
<input type="number" step="0.01" name="cost"
|
<input type="number" step="0.01" name="cost"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Performed By</label>
|
<label class="block text-sm font-medium theme-text mb-1">Performed By</label>
|
||||||
<input type="text" name="performed_by"
|
<input type="text" name="performed_by"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Parts List</label>
|
<label class="block text-sm font-medium theme-text mb-1">Parts List</label>
|
||||||
<input type="text" name="parts_list"
|
<input type="text" name="parts_list"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
<label class="block text-sm font-medium theme-text mb-1">Description</label>
|
||||||
<textarea name="description" rows="3"
|
<textarea name="description" rows="3"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
|
||||||
@@ -149,29 +150,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Maintenance History -->
|
<!-- Maintenance History -->
|
||||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
<div class="theme-surface rounded-lg shadow-sm p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">Maintenance History (<?php echo count($maintenanceItems); ?>)</h3>
|
<h3 class="text-lg font-semibold theme-text mb-4">Maintenance History (<?php echo count($maintenanceItems); ?>)</h3>
|
||||||
|
|
||||||
<?php if (empty($maintenanceItems)): ?>
|
<?php if (empty($maintenanceItems)): ?>
|
||||||
<div class="text-center py-8 text-gray-500">
|
<div class="text-center py-8 theme-text-muted">
|
||||||
<i class="bi bi-tools text-4xl mb-2"></i>
|
<i class="bi bi-tools text-4xl mb-2"></i>
|
||||||
<p>No maintenance records yet</p>
|
<p>No maintenance records yet</p>
|
||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<?php foreach ($maintenanceItems as $item): ?>
|
<?php foreach ($maintenanceItems as $item): ?>
|
||||||
<div class="border border-gray-200 rounded-lg p-4 hover:border-blue-300 transition">
|
<div class="border theme-border rounded-lg p-4 hover:border-blue-500 transition">
|
||||||
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-start gap-3">
|
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-start gap-3">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-2">
|
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 mb-2">
|
||||||
<h4 class="text-lg font-semibold text-gray-800"><?php echo htmlspecialchars($item['name']); ?></h4>
|
<h4 class="text-lg font-semibold theme-text"><?php echo htmlspecialchars($item['name']); ?></h4>
|
||||||
<?php if ($item['cost']): ?>
|
<?php if ($item['cost']): ?>
|
||||||
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm w-fit">
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm w-fit">
|
||||||
$<?php echo number_format($item['cost'], 2); ?>
|
$<?php echo number_format($item['cost'], 2); ?>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2 text-sm text-gray-600 mb-2">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2 text-sm theme-text-muted mb-2">
|
||||||
<div>
|
<div>
|
||||||
<i class="bi bi-calendar3"></i>
|
<i class="bi bi-calendar3"></i>
|
||||||
<?php echo date('M d, Y', strtotime($item['date'])); ?>
|
<?php echo date('M d, Y', strtotime($item['date'])); ?>
|
||||||
@@ -194,7 +195,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($item['description']): ?>
|
<?php if ($item['description']): ?>
|
||||||
<p class="text-sm text-gray-600 mt-2"><?php echo nl2br(htmlspecialchars($item['description'])); ?></p>
|
<p class="text-sm theme-text-muted mt-2"><?php echo nl2br(htmlspecialchars($item['description'])); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex sm:flex-row gap-2 lg:ml-4">
|
<div class="flex sm:flex-row gap-2 lg:ml-4">
|
||||||
@@ -219,51 +220,51 @@
|
|||||||
|
|
||||||
<!-- Edit Vehicle Modal -->
|
<!-- Edit Vehicle Modal -->
|
||||||
<div id="editVehicleModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div id="editVehicleModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
<div class="theme-surface rounded-lg p-6 w-full max-w-md">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-xl font-semibold">Edit Vehicle</h3>
|
<h3 class="text-xl font-semibold theme-text">Edit Vehicle</h3>
|
||||||
<button onclick="hideEditVehicleModal()" class="text-gray-500 hover:text-gray-700">
|
<button onclick="hideEditVehicleModal()" class="theme-text-muted hover:opacity-80">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form method="POST" action="<?php echo url('/vehicles/' . $vehicle['id'] . '/edit'); ?>">
|
<form method="POST" action="<?php echo url('/vehicles/' . $vehicle['id'] . '/edit'); ?>">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Name *</label>
|
||||||
<input type="text" name="name" required value="<?php echo htmlspecialchars($vehicle['name']); ?>"
|
<input type="text" name="name" required value="<?php echo htmlspecialchars($vehicle['name']); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Year</label>
|
<label class="block text-sm font-medium theme-text mb-1">Year</label>
|
||||||
<input type="text" name="year" value="<?php echo htmlspecialchars($vehicle['year'] ?? ''); ?>"
|
<input type="text" name="year" value="<?php echo htmlspecialchars($vehicle['year'] ?? ''); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Color</label>
|
<label class="block text-sm font-medium theme-text mb-1">Color</label>
|
||||||
<input type="text" name="color" value="<?php echo htmlspecialchars($vehicle['color'] ?? ''); ?>"
|
<input type="text" name="color" value="<?php echo htmlspecialchars($vehicle['color'] ?? ''); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Make</label>
|
<label class="block text-sm font-medium theme-text mb-1">Make</label>
|
||||||
<input type="text" name="make" value="<?php echo htmlspecialchars($vehicle['make'] ?? ''); ?>"
|
<input type="text" name="make" value="<?php echo htmlspecialchars($vehicle['make'] ?? ''); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Model</label>
|
<label class="block text-sm font-medium theme-text mb-1">Model</label>
|
||||||
<input type="text" name="model" value="<?php echo htmlspecialchars($vehicle['model'] ?? ''); ?>"
|
<input type="text" name="model" value="<?php echo htmlspecialchars($vehicle['model'] ?? ''); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">License Plate</label>
|
<label class="block text-sm font-medium theme-text mb-1">License Plate</label>
|
||||||
<input type="text" name="license_plate" value="<?php echo htmlspecialchars($vehicle['license_plate'] ?? ''); ?>"
|
<input type="text" name="license_plate" value="<?php echo htmlspecialchars($vehicle['license_plate'] ?? ''); ?>"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-3 mt-6">
|
<div class="flex space-x-3 mt-6">
|
||||||
<button type="button" onclick="hideEditVehicleModal()"
|
<button type="button" onclick="hideEditVehicleModal()"
|
||||||
class="flex-1 px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition">
|
class="flex-1 px-4 py-2 border theme-border rounded-md theme-text-muted hover:opacity-95 transition">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@@ -277,54 +278,54 @@
|
|||||||
|
|
||||||
<!-- Edit Maintenance Modal -->
|
<!-- Edit Maintenance Modal -->
|
||||||
<div id="editMaintenanceModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div id="editMaintenanceModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg p-6 w-full max-w-2xl max-h-screen overflow-y-auto">
|
<div class="theme-surface rounded-lg p-6 w-full max-w-2xl max-h-screen overflow-y-auto">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-xl font-semibold">Edit Maintenance Record</h3>
|
<h3 class="text-xl font-semibold theme-text">Edit Maintenance Record</h3>
|
||||||
<button onclick="hideEditMaintenanceModal()" class="text-gray-500 hover:text-gray-700">
|
<button onclick="hideEditMaintenanceModal()" class="theme-text-muted hover:opacity-80">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="editMaintenanceForm" method="POST">
|
<form id="editMaintenanceForm" method="POST">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Maintenance Name *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Maintenance Name *</label>
|
||||||
<input type="text" name="name" id="editName" required
|
<input type="text" name="name" id="editName" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Date *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Date *</label>
|
||||||
<input type="date" name="date" id="editDate" required
|
<input type="date" name="date" id="editDate" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Mileage *</label>
|
<label class="block text-sm font-medium theme-text mb-1">Mileage *</label>
|
||||||
<input type="number" name="mileage" id="editMileage" required
|
<input type="number" name="mileage" id="editMileage" required
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Cost</label>
|
<label class="block text-sm font-medium theme-text mb-1">Cost</label>
|
||||||
<input type="number" step="0.01" name="cost" id="editCost"
|
<input type="number" step="0.01" name="cost" id="editCost"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Performed By</label>
|
<label class="block text-sm font-medium theme-text mb-1">Performed By</label>
|
||||||
<input type="text" name="performed_by" id="editPerformedBy"
|
<input type="text" name="performed_by" id="editPerformedBy"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Parts List</label>
|
<label class="block text-sm font-medium theme-text mb-1">Parts List</label>
|
||||||
<input type="text" name="parts_list" id="editPartsList"
|
<input type="text" name="parts_list" id="editPartsList"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
<label class="block text-sm font-medium theme-text mb-1">Description</label>
|
||||||
<textarea name="description" id="editDescription" rows="3"
|
<textarea name="description" id="editDescription" rows="3"
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
class="w-full px-3 py-2 border theme-input theme-border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<button type="button" onclick="hideEditMaintenanceModal()"
|
<button type="button" onclick="hideEditMaintenanceModal()"
|
||||||
class="flex-1 px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition">
|
class="flex-1 px-4 py-2 border theme-border rounded-md theme-text-muted hover:opacity-95 transition">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
@@ -339,6 +340,8 @@
|
|||||||
<!-- Delete Vehicle Form -->
|
<!-- Delete Vehicle Form -->
|
||||||
<form id="deleteVehicleForm" method="POST" action="<?php echo url('/vehicles/' . $vehicle['id'] . '/delete'); ?>" class="hidden"></form>
|
<form id="deleteVehicleForm" method="POST" action="<?php echo url('/vehicles/' . $vehicle['id'] . '/delete'); ?>" class="hidden"></form>
|
||||||
|
|
||||||
|
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let debounceTimer;
|
let debounceTimer;
|
||||||
const nameInput = document.getElementById('maintenanceName');
|
const nameInput = document.getElementById('maintenanceName');
|
||||||
@@ -359,7 +362,7 @@
|
|||||||
.then(items => {
|
.then(items => {
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
suggestionsDiv.innerHTML = items.map(item =>
|
suggestionsDiv.innerHTML = items.map(item =>
|
||||||
`<div class="px-3 py-2 hover:bg-gray-100 cursor-pointer" onclick="selectSuggestion('${item.replace(/'/g, "\\'")}')">${item}</div>`
|
`<div class="px-3 py-2 theme-text hover:opacity-95 cursor-pointer" onclick="selectSuggestion('${item.replace(/'/g, "\\'")}')">${item}</div>`
|
||||||
).join('');
|
).join('');
|
||||||
suggestionsDiv.classList.remove('hidden');
|
suggestionsDiv.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user