18 Commits

Author SHA1 Message Date
WallyHackenslacker 7afee7fe63 docs: capture theme-toggle relocation spec and plan 2026-05-17 23:50:51 -04:00
WallyHackenslacker 03c9e3c5a3 fix: move theme toggle into shared top-bar controls 2026-05-17 23:41:00 -04:00
WallyHackenslacker b9f1a115d2 Added agentic files. 2026-05-01 15:58:04 -04:00
WallyHackenslacker a1d6afe503 fix: improve theme consistency and accessibility polish 2026-05-01 15:52:09 -04:00
WallyHackenslacker 30d45c59a9 fix: add fallback colors for settings theme styles 2026-05-01 15:34:12 -04:00
WallyHackenslacker 2eaedab29f fix: improve settings view contrast in dark mode 2026-05-01 15:32:00 -04:00
WallyHackenslacker 6fc7d2b005 feat: integrate dark mode support in settings view 2026-05-01 15:29:16 -04:00
WallyHackenslacker 83cc38d471 fix: make home table divider theming reliable 2026-05-01 15:26:57 -04:00
WallyHackenslacker 7555303036 fix: polish dark mode contrast and toggle placement 2026-05-01 15:24:21 -04:00
WallyHackenslacker ac350238cd fix: complete vehicle view theme class coverage 2026-05-01 15:21:26 -04:00
WallyHackenslacker f39c76e291 feat: integrate dark mode support in vehicle detail view 2026-05-01 15:20:05 -04:00
WallyHackenslacker fa0ad72cac fix: align home view flash and table styles with theme system 2026-05-01 15:16:39 -04:00
WallyHackenslacker 6d73ab0592 feat: integrate dark mode support in home view 2026-05-01 15:14:18 -04:00
WallyHackenslacker df7baa8276 feat: integrate dark mode toggle on auth and setup views 2026-05-01 15:11:28 -04:00
WallyHackenslacker 09565eb3d6 refactor: align theme surface utility with semantic text mapping 2026-05-01 15:09:24 -04:00
WallyHackenslacker 16e29a9b49 feat: add shared theme partial with cookie-based dark mode 2026-05-01 15:06:38 -04:00
WallyHackenslacker bea833a5b7 chore: ignore local worktree directory 2026-05-01 15:04:50 -04:00
Michael Staake ac98a7714a Update README.md 2025-12-28 16:34:51 -08:00
15 changed files with 2081 additions and 287 deletions
+4 -1
View File
@@ -21,4 +21,7 @@ Thumbs.db
.env
# htaccess
.htaccess
.htaccess
# Local worktrees
.worktrees/
+156
View File
@@ -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`).
+2 -2
View File
@@ -39,6 +39,6 @@ docker-compose down
5. If everything is configured correctly, you'll see the setup page
6. Set your password to initialize the database
## Need help? Want to learn more? Go to the official GitHub!
## Need help? Want to learn more?
https://github.com/michaelstaake/mainty
https://michaelstaake.com/projects/mainty/
@@ -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,357 @@
# Theme Toggle Top-Bar Relocation 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:** Move the light/dark toggle from floating right-side UI into top-bar controls (left of settings), with consistent visibility and behavior across all user-facing views.
**Architecture:** Introduce one reusable controls partial (`topbar-controls.php`) that owns toggle/settings markup, then reuse it from each page header. Keep theme state logic centralized in `theme.php` while removing floating positioning and binding behavior to the top-bar toggle IDs. Update all templates in `views/*.php` to ensure complete coverage and consistent top-bar placement.
**Tech Stack:** PHP 8+, server-rendered views, Tailwind CDN utilities, Bootstrap Icons, cookie-based theme persistence.
---
## File Structure and Responsibilities
- Create: `views/partials/topbar-controls.php`
- Single responsibility: render top-bar controls (theme toggle always, settings link optionally).
- Modify: `views/partials/theme.php`
- Single responsibility: theme token CSS + initialization + toggle behavior script.
- Remove floating button markup/CSS; keep runtime toggle logic.
- Modify: `views/index.php`
- Use shared top-bar controls in existing header with settings visible.
- Modify: `views/vehicle.php`
- Use shared top-bar controls in existing header with settings visible.
- Modify: `views/settings.php`
- Use shared top-bar controls in existing header with settings hidden.
- Modify: `views/login.php`
- Add slim top header + controls; preserve centered login card in main content area.
- Modify: `views/setup.php`
- Add slim top header + controls; preserve centered setup card in main content area.
- Modify: `views/export.php`
- Add theme partial includes and slim top header + controls, preserving export content.
## Chunk 1: Shared Controls + Theme Engine Refactor
### Task 1: Add reusable top-bar controls partial
**Files:**
- Create: `views/partials/topbar-controls.php`
- [ ] **Step 1: Write `topbar-controls.php` with explicit include API defaults**
```php
<?php
$showSettings = isset($showSettings) ? (bool) $showSettings : false;
$settingsUrl = isset($settingsUrl) ? (string) $settingsUrl : url('/settings');
?>
<div class="flex items-center space-x-4">
<button
id="themeToggle"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg shadow-sm transition"
aria-label="Switch to dark mode"
title="Switch to dark mode"
aria-pressed="false"
>
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
</button>
<?php if ($showSettings): ?>
<a href="<?php echo htmlspecialchars($settingsUrl); ?>" class="theme-text-muted hover:opacity-95">
<i class="bi bi-gear-fill text-2xl" aria-hidden="true"></i>
<span class="sr-only">Settings</span>
</a>
<?php endif; ?>
</div>
<noscript>
<style>
#themeToggle {
display: none !important;
}
</style>
</noscript>
```
- [ ] **Step 2: Run syntax check for new partial**
Run: `php -l views/partials/topbar-controls.php`
Expected: `No syntax errors detected in views/partials/topbar-controls.php`
- [ ] **Step 3: Commit partial addition**
```bash
git add views/partials/topbar-controls.php
git commit -m "feat: add reusable top-bar controls partial"
```
### Task 2: Refactor `theme.php` to stop rendering floating toggle
**Files:**
- Modify: `views/partials/theme.php`
- [ ] **Step 1: Remove floating toggle CSS rules and keep focus-visible styling**
Replace floating positioning block with only focus-visible rule:
```css
#themeToggle:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
```
- [ ] **Step 2: Remove body-section button markup and keep JS behavior block**
Delete the button HTML + `<noscript>` block from `if ($themeSection === 'body')` and keep only the script that binds to `#themeToggle` and `#themeToggleIcon`.
- [ ] **Step 3: Keep graceful missing-element guard and aria/icon sync**
Ensure this remains present near script start:
```js
if (!toggleButton || !toggleIcon) {
return;
}
```
- [ ] **Step 4: Run syntax check for theme partial**
Run: `php -l views/partials/theme.php`
Expected: `No syntax errors detected in views/partials/theme.php`
- [ ] **Step 5: Commit theme refactor**
```bash
git add views/partials/theme.php
git commit -m "refactor: detach theme toggle UI from theme partial"
```
## Chunk 2: View Integration + Cross-View Verification
### Task 3: Integrate shared controls into existing authenticated headers
**Files:**
- Modify: `views/index.php`
- Modify: `views/vehicle.php`
- Modify: `views/settings.php`
- [ ] **Step 1: Replace direct settings icon block in `views/index.php`**
Use shared controls in header:
```php
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 2: Replace direct settings icon block in `views/vehicle.php`**
Use shared controls in header:
```php
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 3: Replace direct settings icon block in `views/settings.php`**
Use shared controls in header without self-link:
```php
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
```
- [ ] **Step 4: Run syntax checks for updated authenticated templates**
Run: `php -l views/index.php && php -l views/vehicle.php && php -l views/settings.php`
Expected: three `No syntax errors detected` lines.
- [ ] **Step 5: Commit authenticated-header integration**
```bash
git add views/index.php views/vehicle.php views/settings.php
git commit -m "refactor: reuse top-bar controls in authenticated headers"
```
### Task 4: Add top headers and controls to login/setup/export
**Files:**
- Modify: `views/login.php`
- Modify: `views/setup.php`
- Modify: `views/export.php`
- [ ] **Step 1: Update `views/login.php` body layout for top bar + centered main**
Target structure:
```php
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<!-- existing login card -->
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
```
Must preserve unchanged in `views/login.php`:
- Existing flash message blocks.
- Existing login form action/inputs/button labels.
- Existing `themeSection = 'body'` include placement (moved below new main only if needed).
- [ ] **Step 2: Apply same top-bar pattern to `views/setup.php`**
Target structure:
```php
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<!-- existing setup card + requirement messages -->
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
```
Must preserve unchanged in `views/setup.php`:
- System requirements panel and PHP/SQLite checks.
- Setup form action/field names/validation markers.
- Existing error/validation message blocks.
- [ ] **Step 3: Add theme head include to `views/export.php`**
In `<head>`, add:
```php
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
```
- [ ] **Step 4: Add slim top header with shared controls in `views/export.php`**
Near start of `<body>`, add header:
```php
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
```
- [ ] **Step 5: Apply theme layout classes in `views/export.php`**
Make these concrete class updates while preserving content hierarchy:
- Change `<body class="bg-white p-8">` to `<body class="theme-page min-h-screen">`.
- Wrap export content in a `<main class="max-w-4xl mx-auto px-4 py-8">` container beneath the new header.
- Change the vehicle info panel container from `bg-gray-50` to `theme-surface` and add `theme-border` where bordered surfaces are used.
- Keep export typography/content layout unchanged unless required for theme contrast.
- [ ] **Step 6: Add theme body include to `views/export.php`**
Before `</body>`, add:
```php
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
```
- [ ] **Step 7: Run syntax checks for login/setup/export templates**
Run: `php -l views/login.php && php -l views/setup.php && php -l views/export.php`
Expected: three `No syntax errors detected` lines.
- [ ] **Step 8: Commit public/export header integration**
```bash
git add views/login.php views/setup.php views/export.php
git commit -m "feat: surface theme toggle in top bar across public and export views"
```
### Task 5: End-to-end verification of requirements
**Files:**
- Verify runtime behavior only (no file creation)
- [ ] **Step 1: Run full changed-file syntax sweep**
Run:
```bash
php -l views/partials/topbar-controls.php
php -l views/partials/theme.php
php -l views/index.php
php -l views/vehicle.php
php -l views/settings.php
php -l views/login.php
php -l views/setup.php
php -l views/export.php
```
Expected: all files report no syntax errors.
- [ ] **Step 2: Manual runtime verification checklist (JS enabled)**
Validate in browser:
- `/home`, `/vehicles/{id}`, `/settings`, `/login`, `/setup`, and `/vehicles/{id}/export/html` (export page in current routes)
- Toggle is visible in top bar in every view.
- In views with settings, toggle appears immediately left of settings icon.
- Theme changes immediately and persists after reload/navigation.
- Confirm on both desktop and mobile widths that header controls remain visible and usable.
- Confirm route-to-view coverage by checking all files in `views/*.php` render `topbar-controls.php` directly or through a shared header partial (`index`, `vehicle`, `settings`, `login`, `setup`, `export`).
- Confirm export route parity: top-bar controls are present without breaking export content rendering.
- Confirm settings icon is hidden on pages using `$showSettings = false` (`/settings`, `/login`, `/setup`, `/vehicles/{id}/export/html`).
- [ ] **Step 3: Deterministic coverage verification for templates**
Run:
```bash
grep -n "topbar-controls.php" views/index.php views/vehicle.php views/settings.php views/login.php views/setup.php views/export.php
```
Expected:
- Command prints one include line in each required template file.
- Each required template renders top-bar controls (direct include in this plan; if a shared header partial is introduced later, update this check accordingly).
- [ ] **Step 4: Manual runtime verification (JS disabled)**
Validate in browser devtools/no-script mode:
- `#themeToggle` is hidden (noscript rule works).
- Layout remains usable and header spacing is not broken.
- Expected: no major layout shift or overlap in header controls.
- [ ] **Step 5: Manual fallback verification for cookie-write failure**
Validate by blocking cookies in browser tools or private profile:
- Toggle still switches theme in current session/view.
- Reload may revert to resolved default/system theme when cookie persistence is unavailable.
- No JavaScript errors in console during toggle interaction.
- Expected: theme updates immediately on click even without persistence.
- [ ] **Step 6: Commit final polish/fixes from verification (only if changes were needed)**
```bash
git add views/partials/theme.php views/partials/topbar-controls.php views/index.php views/vehicle.php views/settings.php views/login.php views/setup.php views/export.php
git commit -m "fix: finalize top-bar theme toggle behavior and cross-view consistency"
```
If no files changed during verification, skip commit and mark this step complete.
## Implementation Notes
- Keep changes DRY and YAGNI: no additional theme modes, no storage changes, no route/controller changes.
- Use `@superpowers/test-driven-development` discipline where possible in this codebase by keeping each change small and verified immediately with syntax + targeted manual checks.
- Before claiming completion, run `@superpowers/verification-before-completion` checks (syntax + required manual coverage).
@@ -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.
@@ -0,0 +1,161 @@
# Theme Toggle Top-Bar Relocation Design
## Context
The application currently renders the light/dark toggle as a floating fixed button from `views/partials/theme.php`. The requested behavior is to place this control in the top bar, positioned to the left of the settings button when settings is present, while keeping the toggle visible and usable across all views (including `login` and `setup`).
## Goals
- Replace the floating theme toggle with a top-bar-integrated control.
- Keep theme behavior unchanged (cookie persistence, icon/label updates, immediate visual switch).
- Ensure the toggle is visible and reachable in every application view.
- Preserve existing visual patterns (Tailwind + Bootstrap Icons, current card/header language).
Coverage rule:
- Every user-facing template in `views/*.php` must render `views/partials/topbar-controls.php` directly or through a shared header partial.
## Non-Goals
- Redesigning page layouts beyond what is needed to provide a consistent top bar.
- Changing theme color tokens or dark/light palette values.
- Altering authentication or route behavior.
## Proposed Approach
### 1) Shared top-bar controls partial
Create a reusable partial dedicated to top-bar action controls. This partial will render:
- Theme toggle button (always present).
- Settings link (conditionally present).
Ordering rule:
- Theme toggle appears immediately to the left of settings when settings is shown.
This keeps the placement contract explicit and reusable across all pages.
### Interface contract
- Partial path: `views/partials/topbar-controls.php`.
- Include API:
- `$showSettings` (bool): whether to render settings link in this header; default `false` inside partial.
- `$settingsUrl` (string): target URL when settings is rendered; default is applied inside partial as `url('/settings')` when caller does not set it.
- Required markup contract inside the partial:
- Toggle button id: `themeToggle`.
- Toggle icon id: `themeToggleIcon`.
- Server-rendered baseline attributes:
- `aria-label="Switch to dark mode"`
- `title="Switch to dark mode"`
- `aria-pressed="false"`
- icon class starts at `bi bi-moon-stars-fill`
- Toggle appears first in the controls row; settings link appears second when enabled.
- Script contract:
- `views/partials/theme.php` body script binds to `#themeToggle` and `#themeToggleIcon`.
- If either element is missing, script exits without errors.
### Responsibility split
- `views/partials/topbar-controls.php` owns toggle markup, baseline attributes, ordering, optional settings link, and `<noscript>` rule that hides `#themeToggle` when JS is disabled.
- `views/partials/theme.php` owns theme token CSS, initial theme resolution, cookie persistence, and click behavior synchronization.
### 2) Move toggle rendering responsibility
Adjust `views/partials/theme.php` so it no longer emits floating button markup and fixed-position CSS. Keep it focused on:
- Theme pre-hydration script in `<head>`.
- Theme utility classes/variables.
- Toggle behavior script in `<body>` that binds to the top-bar toggle element.
For robust reuse, the behavior script should target stable element IDs/classes used by the shared controls partial and fail gracefully if a toggle is temporarily absent.
### 3) Standardize top bars across all views
Use a top-bar/header region in every view and include the shared controls partial:
- Authenticated pages that already have a header (home/settings/vehicle-related views): insert shared controls in current header area.
- `login` and `setup`: add a slim header at the top with app branding on the left and shared controls on the right.
This ensures the toggle is always visible and consistently located.
Header wrapper ownership:
- To keep scope focused, slim header markup will be duplicated in `login`, `setup`, and `export` rather than introducing another new wrapper partial.
### View coverage matrix
- `views/index.php`: existing header; include `topbar-controls` with `$showSettings = true`.
- `views/vehicle.php`: existing header; include `topbar-controls` with `$showSettings = true`.
- `views/settings.php`: existing header; include `topbar-controls` with `$showSettings = false` (avoid self-link duplication).
- `views/login.php`: add slim header; include `topbar-controls` with `$showSettings = false`.
- `views/setup.php`: add slim header; include `topbar-controls` with `$showSettings = false`.
- `views/export.php`: add theme partial include + slim top bar with `topbar-controls` and `$showSettings = false`, so toggle remains available in export view too.
Login/setup/export layout contract:
- Body changes from full-screen centered container to vertical layout:
- top header (`theme-surface`, app name left, controls right)
- main content wrapper below header
- Main wrapper keeps current centered card behavior using a flex container with min-height calculated to preserve vertical centering beneath header.
## Data Flow and Interaction
Theme state flow remains unchanged:
1. Initial theme resolved from cookie or system preference in head script.
2. HTML root class/attribute updated (`dark`, `data-theme`).
3. Toggle click flips theme, writes cookie, and synchronizes icon + accessibility attributes.
Only UI location changes; no persistence or resolution logic changes are introduced.
## Accessibility and UX Requirements
- Keep `aria-label` and `aria-pressed` synchronized with active theme.
- Preserve keyboard focus visibility (`:focus-visible` ring).
- Keep descriptive button `title` for pointer users.
- No-JS rule: hide the toggle button via `<noscript>` style in `topbar-controls.php` (button is not shown without scripting). Visibility/usability requirements apply to standard JS-enabled runtime.
## Error Handling
- If cookie write fails (restricted storage), visual theme switching still works for current session.
- If a page omits controls unexpectedly, script exits safely without throwing.
Fallback contract for cookie failures:
- During current page/session: toggle applies visual theme immediately.
- After reload/navigation: persisted preference may be unavailable; theme resolves again from cookie/system preference.
- No blocking alert/toast is added in this change.
## Verification Plan
### Automated checks
- Run `php -l` on every changed PHP file.
### Manual checks
- Confirm top-bar toggle presence and operation on:
- Home (`/home`)
- Vehicle detail pages (`/vehicles/:id`)
- Settings (`/settings`)
- Login (`/login`)
- Setup (`/setup`)
- Export (`/vehicles/:id/export/html` via `/vehicles/:id/export/{format}` route)
- Confirm placement: toggle is left of settings where settings exists.
- Confirm responsive behavior on mobile and desktop.
- Confirm theme persistence after navigation and reload.
- Confirm cookie-write failure fallback expectation: in-session switch works; reload may revert when cookie cannot be stored.
- Confirm route-to-view coverage by checking all templates in `views/*.php` include `topbar-controls.php` (currently: `index`, `vehicle`, `settings`, `login`, `setup`, `export`).
## Risks and Mitigations
- **Risk:** Header markup variance across views could cause inconsistent placement.
- **Mitigation:** Centralize controls in one partial and include it in all headers.
- **Risk:** Existing fixed-position CSS may continue affecting layout.
- **Mitigation:** Remove obsolete `#themeToggle` fixed positioning rules during migration.
## Rollout Notes
- Scope is a UI placement refactor with no database changes.
- No migration steps required.
+35 -26
View File
@@ -6,50 +6,58 @@
<title><?php echo htmlspecialchars($vehicle['name']); ?> - Maintenance Export</title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-white p-8">
<div class="max-w-4xl mx-auto">
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="max-w-4xl mx-auto px-4 py-8">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2"><?php echo APP_NAME; ?></h1>
<h2 class="text-xl text-gray-600">Vehicle Maintenance Export</h2>
<h1 class="text-3xl font-bold theme-text mb-2"><?php echo APP_NAME; ?></h1>
<h2 class="text-xl theme-text-muted">Vehicle Maintenance Export</h2>
</div>
<!-- Vehicle Information -->
<div class="bg-gray-50 rounded-lg p-6 mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Vehicle Information</h3>
<div class="theme-surface rounded-lg shadow-sm p-6 mb-8">
<h3 class="text-xl font-semibold theme-text mb-4">Vehicle Information</h3>
<div class="grid grid-cols-2 gap-4">
<div>
<span class="font-medium text-gray-700">Name:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['name']); ?></span>
<span class="font-medium theme-text">Name:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['name']); ?></span>
</div>
<?php if ($vehicle['year']): ?>
<div>
<span class="font-medium text-gray-700">Year:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['year']); ?></span>
<span class="font-medium theme-text">Year:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['year']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['make']): ?>
<div>
<span class="font-medium text-gray-700">Make:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['make']); ?></span>
<span class="font-medium theme-text">Make:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['make']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['model']): ?>
<div>
<span class="font-medium text-gray-700">Model:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['model']); ?></span>
<span class="font-medium theme-text">Model:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['model']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['color']): ?>
<div>
<span class="font-medium text-gray-700">Color:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['color']); ?></span>
<span class="font-medium theme-text">Color:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['color']); ?></span>
</div>
<?php endif; ?>
<?php if ($vehicle['license_plate']): ?>
<div>
<span class="font-medium text-gray-700">License Plate:</span>
<span class="text-gray-600"><?php echo htmlspecialchars($vehicle['license_plate']); ?></span>
<span class="font-medium theme-text">License Plate:</span>
<span class="theme-text-muted"><?php echo htmlspecialchars($vehicle['license_plate']); ?></span>
</div>
<?php endif; ?>
</div>
@@ -57,16 +65,16 @@
<!-- Maintenance History -->
<div>
<h3 class="text-xl font-semibold text-gray-800 mb-4">Maintenance History</h3>
<h3 class="text-xl font-semibold theme-text mb-4">Maintenance History</h3>
<?php if (empty($maintenanceItems)): ?>
<p class="text-gray-500 text-center py-8">No maintenance records available.</p>
<p class="theme-text-muted text-center py-8">No maintenance records available.</p>
<?php else: ?>
<div class="space-y-4">
<?php foreach ($maintenanceItems as $item): ?>
<div class="border border-gray-300 rounded-lg p-4">
<div class="theme-surface border theme-border rounded-lg p-4">
<div class="flex justify-between items-start 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']): ?>
<span class="bg-green-100 text-green-800 px-3 py-1 rounded">
$<?php echo number_format($item['cost'], 2); ?>
@@ -74,7 +82,7 @@
<?php endif; ?>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm text-gray-600 mb-2">
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm theme-text-muted mb-2">
<div>
<strong>Date:</strong> <?php echo date('M d, Y', strtotime($item['date'])); ?>
</div>
@@ -94,7 +102,7 @@
</div>
<?php if ($item['description']): ?>
<div class="mt-2 text-sm text-gray-600">
<div class="mt-2 text-sm theme-text-muted">
<strong>Description:</strong><br>
<?php echo nl2br(htmlspecialchars($item['description'])); ?>
</div>
@@ -105,10 +113,11 @@
<?php endif; ?>
</div>
<div class="mt-8 text-center text-sm text-gray-500">
<div class="mt-8 text-center text-sm theme-text-muted">
<p>Exported on <?php echo date('F j, Y \a\t g:i A'); ?></p>
<p>Generated by <?php echo APP_NAME; ?></p>
</div>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+51 -52
View File
@@ -6,44 +6,41 @@
<title><?php echo APP_NAME; ?> - Vehicles</title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-gray-100 min-h-screen">
<body class="theme-page min-h-screen">
<!-- 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">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 py-8">
<?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']); ?>
</div>
<?php endif; ?>
<?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']); ?>
</div>
<?php endif; ?>
<!-- Header with view toggle and add button -->
<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 bg-white rounded-lg shadow-sm">
<div class="flex theme-surface rounded-lg shadow-sm">
<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>
</a>
<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>
</a>
</div>
@@ -57,10 +54,10 @@
<!-- Vehicles Display -->
<?php if (empty($vehicles)): ?>
<div class="bg-white rounded-lg shadow-sm p-12 text-center">
<i class="bi bi-car-front text-6xl text-gray-300 mb-4"></i>
<h3 class="text-xl font-semibold text-gray-700 mb-2">No vehicles yet</h3>
<p class="text-gray-500 mb-4">Get started by adding your first vehicle</p>
<div class="theme-surface rounded-lg shadow-sm p-12 text-center">
<i class="bi bi-car-front text-6xl theme-text-muted mb-4"></i>
<h3 class="text-xl font-semibold theme-text mb-2">No vehicles yet</h3>
<p class="theme-text-muted mb-4">Get started by adding your first vehicle</p>
<button onclick="showAddVehicleModal()"
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
Add Your First Vehicle
@@ -71,15 +68,15 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<?php foreach ($vehicles as $vehicle): ?>
<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="bg-blue-100 p-3 rounded-lg">
<i class="bi bi-car-front-fill text-blue-600 text-2xl"></i>
</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>
<h3 class="text-lg font-semibold text-gray-800 mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h3>
<div class="space-y-1 text-sm text-gray-600">
<h3 class="text-lg font-semibold theme-text mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h3>
<div class="space-y-1 text-sm theme-text">
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
<p><?php echo implode(' ', array_filter([$vehicle['year'], $vehicle['make'], $vehicle['model']])); ?></p>
<?php endif; ?>
@@ -87,47 +84,47 @@
<p><i class="bi bi-credit-card-2-front"></i> <?php echo htmlspecialchars($vehicle['license_plate']); ?></p>
<?php endif; ?>
<?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; ?>
</div>
</a>
<?php endforeach; ?>
</div>
<?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">
<thead class="bg-gray-50 border-b">
<thead class="theme-surface-alt theme-border border-b">
<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 text-gray-500 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 text-gray-500 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">Vehicle</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 theme-text-muted uppercase tracking-wider">License Plate</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 theme-text-muted uppercase tracking-wider">Last Service</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tbody class="theme-row-dividers">
<?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">
<div class="flex items-center">
<div class="bg-blue-100 p-2 rounded">
<i class="bi bi-car-front-fill text-blue-600"></i>
</div>
<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>
</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']])) ?: '-'; ?>
</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']) ?: '-'; ?>
</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']; ?>
</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'])) : '-'; ?>
</td>
</tr>
@@ -141,51 +138,51 @@
<!-- Add Vehicle Modal -->
<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">
<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>
</button>
</div>
<form method="POST" action="<?php echo url('/vehicles/add'); ?>">
<div class="space-y-4">
<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
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 class="grid grid-cols-2 gap-4">
<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"
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>
<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"
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>
<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"
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>
<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"
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>
<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"
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="flex space-x-3 mt-6">
<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
</button>
<button type="submit"
@@ -196,6 +193,8 @@
</form>
</div>
</div>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
<script>
function showAddVehicleModal() {
+40 -29
View File
@@ -6,36 +6,47 @@
<title><?php echo APP_NAME; ?> - Login</title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="bg-white 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>
<?php if (isset($_SESSION['error'])): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['success'])): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
<?php echo htmlspecialchars($_SESSION['success']); unset($_SESSION['success']); ?>
</div>
<?php endif; ?>
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<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"
placeholder="Enter your password">
</div>
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?></h1>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Login
</button>
</form>
</div>
<?php if (isset($_SESSION['error'])): ?>
<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']); ?>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['success'])): ?>
<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']); ?>
</div>
<?php endif; ?>
<form method="POST" action="<?php echo url('/login'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium theme-text mb-1">Password</label>
<input type="password" id="password" name="password" required autofocus
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">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Login
</button>
</form>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+194
View File
@@ -0,0 +1,194 @@
<?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:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
</style>
<?php
endif;
if ($themeSection === 'body'):
?>
<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;
+31
View File
@@ -0,0 +1,31 @@
<?php
$showSettings = isset($showSettings) ? (bool) $showSettings : false;
$settingsUrl = isset($settingsUrl) ? (string) $settingsUrl : url('/settings');
?>
<div class="flex items-center space-x-4">
<button
id="themeToggle"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg shadow-sm transition"
aria-label="Switch to dark mode"
title="Switch to dark mode"
aria-pressed="false"
>
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
</button>
<?php if ($showSettings): ?>
<a href="<?php echo htmlspecialchars($settingsUrl); ?>" class="theme-text-muted hover:opacity-95">
<i class="bi bi-gear-fill text-2xl" aria-hidden="true"></i>
<span class="sr-only">Settings</span>
</a>
<?php endif; ?>
</div>
<noscript>
<style>
#themeToggle {
display: none !important;
}
</style>
</noscript>
+33 -35
View File
@@ -6,60 +6,57 @@
<title><?php echo APP_NAME; ?> - Settings</title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-gray-100 min-h-screen">
<body class="theme-page min-h-screen">
<!-- 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">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 py-8">
<!-- Breadcrumb -->
<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>
<?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']); ?>
</div>
<?php endif; ?>
<?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']); ?>
</div>
<?php endif; ?>
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-800">Settings</h2>
<h2 class="text-2xl font-bold theme-text">Settings</h2>
<a href="<?php echo url('/logout'); ?>"
class="inline-flex items-center bg-red-100 hover:bg-red-200 text-red-700 px-4 py-2 rounded-md transition">
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">
<!-- Quick Tasks Section -->
<div class="bg-white rounded-lg shadow-sm p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<div class="theme-surface rounded-lg shadow-sm p-6">
<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>
Quick Tasks
</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 -->
<form method="POST" action="<?php echo url('/settings/quick-tasks/add'); ?>" class="mb-4">
<div class="flex space-x-2">
<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"
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
@@ -70,11 +67,11 @@
<!-- Quick Tasks List -->
<div class="space-y-2 max-h-96 overflow-y-auto">
<?php foreach ($quickTasks as $task): ?>
<div class="flex items-center justify-between p-2 hover:bg-gray-50 rounded border border-gray-200">
<span class="text-gray-700"><?php echo htmlspecialchars($task['name']); ?></span>
<div class="flex items-center justify-between p-2 theme-surface-alt hover:opacity-95 rounded border theme-border">
<span class="theme-text"><?php echo htmlspecialchars($task['name']); ?></span>
<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">
<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>
</button>
</form>
@@ -84,31 +81,31 @@
</div>
<!-- Change Password Section -->
<div class="bg-white rounded-lg shadow-sm p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
<div class="theme-surface rounded-lg shadow-sm p-6">
<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>
Change Password
</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">
<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
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>
<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
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">
</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
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>
<button type="submit"
@@ -120,20 +117,21 @@
</div>
<!-- Powered by Mainty Section -->
<div class="mt-6 bg-white rounded-lg shadow-sm p-6 text-center">
<h3 class="text-lg font-semibold text-gray-800 mb-2 flex items-center justify-center">
<i class="bi bi-github text-gray-700 mr-2"></i>
<div class="mt-6 theme-surface rounded-lg shadow-sm p-6 text-center">
<h3 class="text-lg font-semibold theme-text mb-2 flex items-center justify-center">
<i class="bi bi-github theme-text-muted mr-2"></i>
Powered by Mainty, a project by Michael Staake and the community.
</h3>
<p class="text-sm text-gray-600 mb-3">
<p class="text-sm theme-text-muted mb-3">
Get the latest version, learn more, or report issues on the official project GitHub.
</p>
<a href="https://github.com/michaelstaake/mainty" target="_blank" rel="noopener noreferrer"
class="inline-flex items-center text-blue-600 hover:text-blue-800 font-medium">
<i class="bi bi-box-arrow-up-right mr-1"></i>
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>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+75 -64
View File
@@ -6,73 +6,84 @@
<title><?php echo APP_NAME; ?> - Setup</title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="bg-white 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>
<?php if (isset($_SESSION['error'])): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<?php echo htmlspecialchars($_SESSION['error']); unset($_SESSION['error']); ?>
</div>
<?php endif; ?>
<div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded">
<h2 class="font-semibold text-blue-900 mb-2">System Requirements</h2>
<ul class="space-y-1 text-sm">
<li class="flex items-center">
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>PHP Version: <?php echo PHP_VERSION; ?>
<?php if (version_compare(PHP_VERSION, '8.0.0', '>=')): ?>
<span class="text-green-600">(✓ Meets requirement)</span>
<?php else: ?>
<span class="text-red-600">(✗ Requires 8.0+)</span>
<?php endif; ?>
</span>
</li>
<li class="flex items-center">
<?php if (extension_loaded('pdo_sqlite')): ?>
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>SQLite: <span class="text-green-600">Available (✓)</span></span>
<?php else: ?>
<i class="bi bi-x-circle-fill text-red-600 mr-2"></i>
<span>SQLite: <span class="text-red-600">Not Available (✗)</span></span>
<?php endif; ?>
</li>
</ul>
<body class="theme-page min-h-screen">
<header class="theme-surface shadow-sm">
<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 theme-text hover:opacity-95 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = false; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
<?php
$canSetup = version_compare(PHP_VERSION, '8.0.0', '>=') && extension_loaded('pdo_sqlite');
?>
<?php if (!$canSetup): ?>
<div class="bg-yellow-100 border border-yellow-400 text-yellow-800 px-4 py-3 rounded mb-4">
<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>
</header>
<main class="min-h-[calc(100vh-72px)] flex items-center justify-center px-4 py-8">
<div class="theme-surface p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-3xl font-bold theme-text mb-6 text-center"><?php echo APP_NAME; ?> Setup</h1>
<?php if (isset($_SESSION['error'])): ?>
<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']); ?>
</div>
<?php endif; ?>
<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 mb-2">System Requirements</h2>
<ul class="space-y-1 text-sm">
<li class="flex items-center">
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>PHP Version: <?php echo PHP_VERSION; ?>
<?php if (version_compare(PHP_VERSION, '8.0.0', '>=')): ?>
<span class="text-green-600">(✓ Meets requirement)</span>
<?php else: ?>
<span class="text-red-600">(✗ Requires 8.0+)</span>
<?php endif; ?>
</span>
</li>
<li class="flex items-center">
<?php if (extension_loaded('pdo_sqlite')): ?>
<i class="bi bi-check-circle-fill text-green-600 mr-2"></i>
<span>SQLite: <span class="text-green-600">Available (✓)</span></span>
<?php else: ?>
<i class="bi bi-x-circle-fill text-red-600 mr-2"></i>
<span>SQLite: <span class="text-red-600">Not Available (✗)</span></span>
<?php endif; ?>
</li>
</ul>
</div>
<?php else: ?>
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Create Password</label>
<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"
placeholder="Enter password (min 6 characters)">
<?php
$canSetup = version_compare(PHP_VERSION, '8.0.0', '>=') && extension_loaded('pdo_sqlite');
?>
<?php if (!$canSetup): ?>
<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="text-sm mt-1">Please ensure PHP 8.0+ and SQLite extension are available.</p>
</div>
<div>
<label for="confirm_password" class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label>
<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"
placeholder="Confirm password">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Complete Setup
</button>
</form>
<?php endif; ?>
</div>
<?php else: ?>
<form method="POST" action="<?php echo url('/setup'); ?>" class="space-y-4">
<div>
<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
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)">
</div>
<div>
<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
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">
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">
Complete Setup
</button>
</form>
<?php endif; ?>
</div>
</main>
<?php $themeSection = 'body'; include __DIR__ . '/partials/theme.php'; ?>
</body>
</html>
+77 -78
View File
@@ -6,48 +6,45 @@
<title><?php echo APP_NAME; ?> - <?php echo htmlspecialchars($vehicle['name']); ?></title>
<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">
<?php $themeSection = 'head'; include __DIR__ . '/partials/theme.php'; ?>
</head>
<body class="bg-gray-100 min-h-screen">
<body class="theme-page min-h-screen">
<!-- 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">
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold text-gray-800 hover:text-gray-600 transition"><?php echo APP_NAME; ?></a>
<div class="flex items-center space-x-4">
<a href="<?php echo url('/settings'); ?>" class="text-gray-600 hover:text-gray-800">
<i class="bi bi-gear-fill text-2xl"></i>
</a>
</div>
<a href="<?php echo url('/home'); ?>" class="text-2xl font-bold theme-text hover:opacity-90 transition"><?php echo APP_NAME; ?></a>
<?php $showSettings = true; include __DIR__ . '/partials/topbar-controls.php'; ?>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 py-8">
<!-- Breadcrumb -->
<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>
<?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']); ?>
</div>
<?php endif; ?>
<?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']); ?>
</div>
<?php endif; ?>
<!-- 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 sm:flex-row items-start gap-4">
<div class="bg-blue-100 p-4 rounded-lg">
<i class="bi bi-car-front-fill text-blue-600 text-3xl"></i>
</div>
<div>
<h2 class="text-2xl font-bold text-gray-800 mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h2>
<div class="space-y-1 text-gray-600">
<h2 class="text-2xl font-bold theme-text mb-2"><?php echo htmlspecialchars($vehicle['name']); ?></h2>
<div class="space-y-1 theme-text-muted">
<?php if ($vehicle['year'] || $vehicle['make'] || $vehicle['model']): ?>
<p class="flex items-center">
<i class="bi bi-info-circle mr-2"></i>
@@ -71,7 +68,7 @@
</div>
<div class="flex flex-wrap gap-2">
<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>
</button>
<div class="relative">
@@ -79,13 +76,13 @@
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>
</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'); ?>"
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
</a>
<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
</a>
</div>
@@ -99,47 +96,47 @@
</div>
<!-- Add Maintenance Item Card -->
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Add Maintenance Record</h3>
<div class="theme-surface rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-semibold theme-text mb-4">Add Maintenance Record</h3>
<form method="POST" action="<?php echo url('/maintenance/add'); ?>">
<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>
<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"
class="w-full px-3 py-2 border border-gray-300 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>
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 theme-surface border theme-border rounded-md shadow-lg mt-1 max-h-48 overflow-y-auto"></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'); ?>"
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>
<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
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>
<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"
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>
<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"
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>
<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"
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="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"
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>
<button type="submit"
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition">
@@ -149,29 +146,29 @@
</div>
<!-- Maintenance History -->
<div class="bg-white rounded-lg shadow-sm p-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Maintenance History (<?php echo count($maintenanceItems); ?>)</h3>
<div class="theme-surface rounded-lg shadow-sm p-6">
<h3 class="text-lg font-semibold theme-text mb-4">Maintenance History (<?php echo count($maintenanceItems); ?>)</h3>
<?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>
<p>No maintenance records yet</p>
</div>
<?php else: ?>
<div class="space-y-4">
<?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-1">
<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']): ?>
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm w-fit">
$<?php echo number_format($item['cost'], 2); ?>
</span>
<?php endif; ?>
</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>
<i class="bi bi-calendar3"></i>
<?php echo date('M d, Y', strtotime($item['date'])); ?>
@@ -194,7 +191,7 @@
<?php endif; ?>
</div>
<?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; ?>
</div>
<div class="flex sm:flex-row gap-2 lg:ml-4">
@@ -219,51 +216,51 @@
<!-- Edit Vehicle Modal -->
<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">
<h3 class="text-xl font-semibold">Edit Vehicle</h3>
<button onclick="hideEditVehicleModal()" class="text-gray-500 hover:text-gray-700">
<h3 class="text-xl font-semibold theme-text">Edit Vehicle</h3>
<button onclick="hideEditVehicleModal()" class="theme-text-muted hover:opacity-80">
<i class="bi bi-x-lg"></i>
</button>
</div>
<form method="POST" action="<?php echo url('/vehicles/' . $vehicle['id'] . '/edit'); ?>">
<div class="space-y-4">
<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']); ?>"
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 class="grid grid-cols-2 gap-4">
<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'] ?? ''); ?>"
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>
<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'] ?? ''); ?>"
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>
<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'] ?? ''); ?>"
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>
<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'] ?? ''); ?>"
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>
<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'] ?? ''); ?>"
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="flex space-x-3 mt-6">
<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
</button>
<button type="submit"
@@ -277,54 +274,54 @@
<!-- Edit Maintenance Modal -->
<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">
<h3 class="text-xl font-semibold">Edit Maintenance Record</h3>
<button onclick="hideEditMaintenanceModal()" class="text-gray-500 hover:text-gray-700">
<h3 class="text-xl font-semibold theme-text">Edit Maintenance Record</h3>
<button onclick="hideEditMaintenanceModal()" class="theme-text-muted hover:opacity-80">
<i class="bi bi-x-lg"></i>
</button>
</div>
<form id="editMaintenanceForm" method="POST">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<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
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>
<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
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>
<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
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>
<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"
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>
<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"
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>
<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"
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="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"
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 class="flex space-x-3">
<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
</button>
<button type="submit"
@@ -338,6 +335,8 @@
<!-- Delete Vehicle 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>
let debounceTimer;
@@ -359,7 +358,7 @@
.then(items => {
if (items.length > 0) {
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('');
suggestionsDiv.classList.remove('hidden');
} else {