369 lines
13 KiB
Markdown
369 lines
13 KiB
Markdown
# CLAUDE.md — Damascus Parking (SCP-Syria)
|
||
|
||
## Working Rules
|
||
|
||
### Modal Close Button Must Be on the Far Left in RTL
|
||
|
||
Bootstrap compiles `.modal-header .btn-close` with physical `margin-left: auto`, which in RTL pushes the × button to the right (next to the title text) instead of to the far left end of the header.
|
||
|
||
**The fix is already applied globally** in the `[dir="rtl"]` block at the bottom of `app.scss`:
|
||
```scss
|
||
[dir="rtl"] .modal-header .btn-close {
|
||
margin-left: 0;
|
||
margin-right: auto;
|
||
}
|
||
```
|
||
|
||
**Rule: never add inline styles or per-modal hacks to reposition the close button — the global fix handles it. Do not remove this override.**
|
||
|
||
---
|
||
|
||
### Never Open Links in a New Tab
|
||
|
||
**Hard rule. Links must never use `target="_blank"` unless the user explicitly requests it.**
|
||
|
||
- Remove `target="_blank"` and `rel="noopener"` from all `<a>` tags unless told otherwise.
|
||
- This applies to every view, every layout, every page — no exceptions.
|
||
|
||
---
|
||
|
||
### Never Use JavaScript alert() / confirm() / prompt()
|
||
|
||
**Hard rule. These native browser dialogs are ugly and break the UI.**
|
||
|
||
- Never use `alert(...)`, `confirm(...)`, or `prompt(...)` anywhere in JavaScript.
|
||
- For confirmations: build a Bootstrap modal with Cancel / Confirm buttons.
|
||
- For success/error feedback: use a toast notification or an inline alert element.
|
||
- This applies to every view, every page, every script — no exceptions.
|
||
|
||
---
|
||
|
||
### Mandatory: Branch Before Every Change
|
||
|
||
**This is a hard rule. Never modify any file without first creating a git branch and committing the current state.**
|
||
|
||
1. Create a branch named after the feature or fix being worked on
|
||
2. Stage and commit every file that will be touched — this is the rollback point
|
||
3. Do **not** push to remote
|
||
4. Then make the changes on that same branch
|
||
|
||
```bash
|
||
git checkout -b feature/my-feature-name
|
||
git add <files being changed>
|
||
git commit -m "backup: before <description of change>"
|
||
# now make the changes
|
||
```
|
||
|
||
To revert a file: `git checkout <branch>^ -- path/to/file`
|
||
To revert everything: `git checkout <branch>^`
|
||
|
||
---
|
||
|
||
## Project Overview
|
||
|
||
**Damascus Parking** (دمشق باركينغ) is a full-stack web application for parking lot management in Damascus, Syria. It allows the public to search and book parking spaces, operators to manage vehicle check-ins/check-outs, and admins to oversee all operations.
|
||
|
||
- **Framework:** Laravel 12.0 (PHP ^8.2)
|
||
- **Database:** SQLite (`database/database.sqlite`)
|
||
- **Auth:** Laravel Sanctum 4.0 (session-based for web, token-based for API)
|
||
- **Frontend:** Vite 7 + Bootstrap 5.3 + Sass + Leaflet maps
|
||
- **Language/Layout:** Arabic (RTL throughout), font: Cairo (Google Fonts)
|
||
- **Timezone:** Asia/Damascus
|
||
- **Local URL:** http://localhost:8000
|
||
|
||
---
|
||
|
||
## Development Setup
|
||
|
||
```bash
|
||
# Full install + migrate + build
|
||
composer setup
|
||
|
||
# Start all dev services (server, queue, logs, Vite HMR)
|
||
composer dev
|
||
|
||
# OR individually:
|
||
php artisan serve
|
||
npm run dev
|
||
|
||
# Run tests
|
||
composer test
|
||
# or: php artisan config:clear && php artisan test
|
||
```
|
||
|
||
**Vite entry points:** `resources/css/app.scss`, `resources/js/app.js`
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### Directory Structure
|
||
|
||
```
|
||
app/
|
||
Http/
|
||
Controllers/
|
||
Admin/ # Admin-only: Dashboard, ParkingLot, Booking
|
||
Api/ # REST API: ParkingLot, Booking, CarRegistry
|
||
Operator/ # Operator-only: OperatorController
|
||
ParkingController.php # Public landing page
|
||
Middleware/
|
||
EnsureAdmin.php
|
||
EnsureOperator.php
|
||
Requests/ # Form validation: Booking, CheckIn, ParkingLot
|
||
Resources/ # API response transformers
|
||
Models/
|
||
User.php # Roles: admin | operator | user
|
||
ParkingLot.php # Scopes: active(), withStatus(); computed attributes
|
||
Booking.php # Status enum: active | completed | cancelled
|
||
CarRegistry.php # Scope: active() (exit_time IS NULL)
|
||
routes/
|
||
web.php # Pages + auth
|
||
api.php # /api/v1/* REST endpoints
|
||
auth.php # Login/register/logout
|
||
resources/
|
||
views/
|
||
admin/ # Admin Blade templates
|
||
operator/ # Operator Blade templates
|
||
auth/ # Login & register pages
|
||
layouts/ # Base layouts
|
||
index.blade.php # Public landing page
|
||
css/app.scss # ~780 lines of custom RTL-aware styles
|
||
js/app.js # Bootstrap + Axios setup
|
||
```
|
||
|
||
### Roles & Access
|
||
|
||
| Role | Middleware | Access |
|
||
|------------|-----------------|---------------------------------------------|
|
||
| `admin` | `EnsureAdmin` | Full admin dashboard, parking lot CRUD, all bookings |
|
||
| `operator` | `EnsureOperator`| Vehicle check-in/check-out, active bookings for assigned lot |
|
||
| `user` | (auth only) | Public search, create bookings via API |
|
||
|
||
Both middleware classes return Arabic 403 messages on unauthorized access.
|
||
|
||
---
|
||
|
||
## Database Schema
|
||
|
||
### `users`
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| id | bigint PK | |
|
||
| name | string | |
|
||
| email | string unique | |
|
||
| password | string | bcrypt |
|
||
| role | string | 'admin' \| 'operator' \| 'user' (default) |
|
||
|
||
### `parking_lots`
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| id | bigint PK | |
|
||
| name | string | |
|
||
| address | string | |
|
||
| total_capacity | integer | |
|
||
| price_per_hour | decimal | |
|
||
| latitude / longitude | decimal | Used by Leaflet map |
|
||
| working_hours | string | Default '24/7' |
|
||
| is_active | boolean | Toggleable by admin |
|
||
|
||
Computed attributes (not stored): `available_spaces`, `occupied_spaces`, `usage_percentage`
|
||
|
||
### `bookings`
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| parking_lot_id | FK | |
|
||
| customer_name | string | |
|
||
| phone | string | Format: `09xxxxxxxx` |
|
||
| start_time / end_time | datetime | |
|
||
| status | enum | 'active' \| 'completed' \| 'cancelled' |
|
||
|
||
### `car_registries`
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| parking_lot_id | FK | |
|
||
| plate_number | string | |
|
||
| entry_time | datetime | |
|
||
| exit_time | datetime nullable | NULL = currently parked |
|
||
|
||
---
|
||
|
||
## API Reference (`/api/v1`)
|
||
|
||
All responses follow: `{ "success": bool, "data": ..., "message": "..." }`
|
||
|
||
### Parking Lots
|
||
```
|
||
GET /api/v1/parking-lots # Paginated list with occupancy status
|
||
GET /api/v1/parking-lots/{id} # Single lot details
|
||
GET /api/v1/parking-lots/search?q= # Search by name/address
|
||
GET /api/v1/parking-lots/{id}/status # Real-time occupancy
|
||
```
|
||
|
||
### Bookings
|
||
```
|
||
POST /api/v1/bookings # Create booking
|
||
GET /api/v1/bookings # Recent bookings (paginated)
|
||
```
|
||
Required fields: `parking_lot_id`, `customer_name`, `phone` (09xxxxxxxx), `start_time`, `end_time`
|
||
|
||
### Car Registry (Check-in/Check-out)
|
||
```
|
||
POST /api/v1/car-registries # Register car entry
|
||
PUT /api/v1/car-registries/{id}/exit # Register car exit
|
||
```
|
||
Check-in requires: `parking_lot_id`, `vehicle_plate`, `duration_hours` (0.25–24)
|
||
|
||
---
|
||
|
||
## Web Routes Summary
|
||
|
||
| Path | Middleware | Description |
|
||
|------|-----------|-------------|
|
||
| `GET /` | — | Public landing page |
|
||
| `GET /login` | guest | Login form |
|
||
| `GET /register` | guest | Register form |
|
||
| `POST /logout` | auth | Logout |
|
||
| `GET /admin/dashboard` | admin | Admin stats & charts |
|
||
| `GET /admin/parking-lots` | admin | List/manage parking lots |
|
||
| `POST /admin/parking-lots` | admin | Create parking lot |
|
||
| `PUT /admin/parking-lots/{id}` | admin | Update parking lot |
|
||
| `POST /admin/parking-lots/{id}/toggle` | admin | Toggle active status |
|
||
| `GET /admin/bookings/active` | admin | Active bookings |
|
||
| `POST /admin/bookings/{id}/complete` | admin | Mark booking complete |
|
||
| `GET /operator/dashboard` | operator | Operator view |
|
||
| `POST /operator/check-in` | operator | Vehicle entry |
|
||
| `POST /operator/{booking}/checkout` | operator | Vehicle exit + fee |
|
||
| `GET /admin/stats` | admin | JSON stats (AJAX) |
|
||
| `GET /admin/charts` | admin | JSON chart data (AJAX) |
|
||
|
||
---
|
||
|
||
## Validation Rules
|
||
|
||
**Phone:** must match `^09[0-9]{8}$` (Syrian mobile format)
|
||
|
||
**Booking:**
|
||
- `start_time`: required, after:now
|
||
- `end_time`: required, after:start_time
|
||
- Custom: parking lot must have available capacity
|
||
|
||
**Parking Lot:**
|
||
- `total_capacity`: integer, 1–10000
|
||
- `price_per_hour`: numeric, 0.01–1000
|
||
- `latitude`: -90 to 90, `longitude`: -180 to 180
|
||
|
||
**Operator Check-in:**
|
||
- `duration_hours`: numeric, 0.25–24
|
||
|
||
---
|
||
|
||
## Frontend & Styling
|
||
|
||
**Color palette (CSS variables in `app.scss`):**
|
||
- Primary: `#6366f1` (indigo)
|
||
- Sidebar bg: `#0f172a`
|
||
- Muted text: `#64748b`
|
||
- Page bg: `#f1f5f9`
|
||
|
||
**RTL-specific patterns:**
|
||
- `direction: rtl` on `<html>`
|
||
- Input groups reverse border-radius via CSS overrides in `[dir="rtl"]` block in `app.scss`
|
||
- Flexbox rows appear mirrored naturally; no special JS needed
|
||
- Margin/padding use `inset-inline-*` where needed
|
||
|
||
**Bootstrap RTL spacing — critical rule:**
|
||
Bootstrap is imported as SCSS (`@import "bootstrap/scss/bootstrap"`), which compiles `me-*` to physical `margin-right` and `ms-*` to `margin-left`. In RTL, icons sit on the **right** and text on the **left**, so `margin-right` on an icon pushes *away* from the text — the gap appears on the wrong side.
|
||
|
||
**The fix** is already applied in the `[dir="rtl"]` block at the bottom of `app.scss`: it swaps all `me-*`/`ms-*` physical margins so they behave like Bootstrap's logical properties.
|
||
|
||
**Rule: never add `me-*` to fix icon-to-text spacing — it already works correctly via the global `[dir="rtl"]` override. Do not remove or change those overrides.** If a new icon appears stuck to its label, the cause is always the same: Bootstrap's LTR SCSS compilation. The fix is already in place globally — just use the standard Bootstrap spacing classes (`me-1`, `me-2`, etc.) and they will work correctly in RTL.
|
||
|
||
**Responsive breakpoints:**
|
||
- `<768px`: sidebar hidden, bottom nav visible (56px)
|
||
- `768px–992px`: sidebar can toggle
|
||
- `>992px`: full sidebar + content layout
|
||
|
||
**Libraries:**
|
||
- Leaflet 1.9.4 — interactive parking lot map
|
||
- Bootstrap Icons 1.11.3 — icon set
|
||
- Axios 1.11.0 — AJAX calls from admin/operator JS
|
||
|
||
---
|
||
|
||
## Fee Calculation Logic
|
||
|
||
```php
|
||
$duration = ceil(now()->diffInHours($entry_time)); // rounds up
|
||
$fee = $duration * $parking_lot->price_per_hour;
|
||
```
|
||
|
||
Fees are always rounded up to the nearest hour.
|
||
|
||
---
|
||
|
||
## Availability Calculation
|
||
|
||
Available spaces = `total_capacity` − (active bookings count + active car registries count)
|
||
|
||
`CarRegistry::active()` scope: `whereNull('exit_time')` (or `exit_time > now()`)
|
||
`Booking::active()` scope: `where('status', 'active')`
|
||
|
||
Overbooking is prevented at API validation time in `StoreBookingRequest`.
|
||
|
||
---
|
||
|
||
## Key Artisan Commands
|
||
|
||
```bash
|
||
php artisan migrate # Run migrations
|
||
php artisan migrate:fresh --seed # Reset DB and seed
|
||
php artisan tinker # REPL for debugging
|
||
php artisan route:list # List all routes
|
||
php artisan config:clear # Clear config cache
|
||
php artisan make:model Foo -mcr # Model + migration + controller + resource
|
||
```
|
||
|
||
---
|
||
|
||
## Testing
|
||
|
||
Tests live in `tests/Feature/` and `tests/Unit/`. PHPUnit 11 is configured in `phpunit.xml`. The test database uses the SQLite in-memory driver by default.
|
||
|
||
```bash
|
||
composer test
|
||
# or
|
||
php artisan config:clear && php artisan test
|
||
```
|
||
|
||
---
|
||
|
||
## User-Facing Pages (authenticated)
|
||
|
||
| Route | Name | Description |
|
||
|-------|------|-------------|
|
||
| `GET /profile` | `profile.show` | User info + edit name + change password |
|
||
| `PATCH /profile` | `profile.update` | Update name (error bag: `updateName`) |
|
||
| `PATCH /profile/password` | `profile.password` | Change password (error bag: `updatePassword`) |
|
||
| `GET /dashboard` | `user.dashboard` | User's booking history + stats |
|
||
|
||
**Profile dropdown** is a Blade partial at `resources/views/partials/user-dropdown.blade.php`. It is included in both `layouts/user.blade.php` and `index.blade.php`. When the user is a guest it shows the login button; when authenticated it shows an avatar circle with a dropdown containing profile, reservations, operator/admin links (role-gated), and sign-out.
|
||
|
||
**Post-login redirect** (in `routes/auth.php`) is role-based:
|
||
- `admin` → `/admin/dashboard`
|
||
- `operator` → `/operator/dashboard`
|
||
- `user` → `/dashboard`
|
||
|
||
**`bookings.user_id`** — nullable FK added via migration `2026_04_15_072226`. Existing seeded bookings have `user_id = NULL`. New bookings made by a logged-in user should set `user_id = Auth::id()`.
|
||
|
||
---
|
||
|
||
## Notes & Known Patterns
|
||
|
||
- Admin stats (`/admin/stats`, `/admin/charts`) are fetched via AJAX on page load — not cached, computed fresh each request.
|
||
- The `CarRegistry` model tracks physical vehicle presence; `Booking` tracks reservations — they are separate but both count toward capacity.
|
||
- `StoreParkingLotRequest` and `StoreBookingRequest` are in `app/Http/Requests/`.
|
||
- Arabic error messages are returned by both middleware and validation responses.
|
||
- The `TODO.md` at the root is nearly empty — "Update with checked" is the only entry.
|
||
- Some API routes have commented-out `auth:sanctum` middleware; add it back before deploying to production.
|