- Implement test script for new SAG module endpoints BE-003 (Tag State Management) and BE-004 (Bulk Operations). - Create test cases for creating, updating, and bulk operations on cases and tags. - Add a test for module deactivation to ensure data integrity is maintained. - Include setup and teardown for tests to clear database state before and after each test.
454 lines
12 KiB
Markdown
454 lines
12 KiB
Markdown
# Location Module Templates - Quick Reference Guide
|
|
|
|
## Template Overview
|
|
|
|
5 production-ready Jinja2 templates for the Location (Lokaliteter) module:
|
|
|
|
| Template | Purpose | Context | Key Features |
|
|
|----------|---------|---------|--------------|
|
|
| **list.html** | List all locations | `locations`, `total`, `page_number`, `total_pages`, `filters` | Pagination, bulk select, filters, responsive table |
|
|
| **detail.html** | View location details | `location`, `location.*` (contacts, hours, services, capacity) | 6 tabs, modals, CRUD operations, progress bars |
|
|
| **create.html** | Create new location | `location_types` | 5-section form, validation, character counter |
|
|
| **edit.html** | Edit location | `location`, `location_types` | Pre-filled form, delete modal, PATCH request |
|
|
| **map.html** | Interactive map | `locations`, `location_types` | Leaflet.js, clustering, type filters |
|
|
|
|
---
|
|
|
|
## Directory
|
|
|
|
```
|
|
/app/modules/locations/templates/
|
|
```
|
|
|
|
---
|
|
|
|
## Integration Points
|
|
|
|
### 1. Routes Required (Backend)
|
|
|
|
```python
|
|
@router.get("/locations", response_model=List[Location])
|
|
def list_locations(skip: int = 0, limit: int = 10, ...):
|
|
# Return filtered, paginated locations
|
|
|
|
@router.get("/locations/create", ...)
|
|
def create_page(location_types: List[str]):
|
|
# Render create.html
|
|
|
|
@router.get("/locations/{id}", response_model=LocationDetail)
|
|
def detail_page(id: int):
|
|
# Render detail.html with full object
|
|
|
|
@router.get("/locations/{id}/edit", ...)
|
|
def edit_page(id: int, location_types: List[str]):
|
|
# Render edit.html with location pre-filled
|
|
|
|
@router.get("/locations/map", ...)
|
|
def map_page(locations: List[Location], location_types: List[str]):
|
|
# Render map.html with location data
|
|
```
|
|
|
|
### 2. API Endpoints Required
|
|
|
|
```
|
|
POST /api/v1/locations - Create location
|
|
GET /api/v1/locations/{id} - Get location
|
|
PATCH /api/v1/locations/{id} - Update location
|
|
DELETE /api/v1/locations/{id} - Delete location (soft)
|
|
|
|
POST /api/v1/locations/{id}/contacts - Add contact
|
|
DELETE /api/v1/locations/{id}/contacts/{cid} - Delete contact
|
|
|
|
POST /api/v1/locations/{id}/services - Add service
|
|
DELETE /api/v1/locations/{id}/services/{sid} - Delete service
|
|
|
|
POST /api/v1/locations/{id}/capacity - Add capacity
|
|
DELETE /api/v1/locations/{id}/capacity/{cid} - Delete capacity
|
|
```
|
|
|
|
---
|
|
|
|
## Context Variables Reference
|
|
|
|
### list.html
|
|
```python
|
|
{
|
|
'locations': List[Location],
|
|
'total': int,
|
|
'skip': int,
|
|
'limit': int,
|
|
'page_number': int,
|
|
'total_pages': int,
|
|
'location_type': Optional[str],
|
|
'is_active': Optional[bool],
|
|
'location_types': List[str]
|
|
}
|
|
```
|
|
|
|
### detail.html
|
|
```python
|
|
{
|
|
'location': LocationDetail, # With all nested data
|
|
'location.id': int,
|
|
'location.name': str,
|
|
'location.location_type': str,
|
|
'location.is_active': bool,
|
|
'location.address_*': str,
|
|
'location.phone': str,
|
|
'location.email': str,
|
|
'location.contacts': List[Contact],
|
|
'location.operating_hours': List[Hours],
|
|
'location.services': List[Service],
|
|
'location.capacity': List[Capacity],
|
|
'location.audit_log': List[AuditEntry],
|
|
'location_types': List[str]
|
|
}
|
|
```
|
|
|
|
### create.html
|
|
```python
|
|
{
|
|
'location_types': List[str]
|
|
}
|
|
```
|
|
|
|
### edit.html
|
|
```python
|
|
{
|
|
'location': Location, # Pre-fill values
|
|
'location_types': List[str]
|
|
}
|
|
```
|
|
|
|
### map.html
|
|
```python
|
|
{
|
|
'locations': List[Location], # Must have: id, name, latitude, longitude, location_type, address_city
|
|
'location_types': List[str]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## CSS Classes Used
|
|
|
|
### Bootstrap 5 Classes
|
|
```
|
|
Container: container-fluid, px-4, py-4
|
|
Grid: row, col-*, col-md-*, col-lg-*
|
|
Cards: card, card-body, card-header
|
|
Forms: form-control, form-select, form-check, form-label
|
|
Buttons: btn, btn-primary, btn-outline-secondary, btn-danger
|
|
Tables: table, table-hover, table-responsive
|
|
Badges: badge, bg-success, bg-secondary
|
|
Modals: modal, modal-dialog, modal-content
|
|
Alerts: alert, alert-danger
|
|
Pagination: pagination, page-item, page-link
|
|
Utilities: d-flex, gap-*, justify-content-*, align-items-*
|
|
```
|
|
|
|
### Custom CSS Variables (from base.html)
|
|
```css
|
|
--bg-body: #f8f9fa / #212529
|
|
--bg-card: #ffffff / #2c3034
|
|
--text-primary: #2c3e50 / #f8f9fa
|
|
--text-secondary: #6c757d / #adb5bd
|
|
--accent: #0f4c75 / #3d8bfd
|
|
--accent-light: #eef2f5 / #373b3e
|
|
--border-radius: 12px
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript Events
|
|
|
|
### list.html
|
|
- Checkbox select/deselect
|
|
- Bulk delete confirmation
|
|
- Individual delete confirmation
|
|
- Row click navigation
|
|
- Page navigation
|
|
|
|
### detail.html
|
|
- Tab switching (Bootstrap nav-tabs)
|
|
- Modal open/close
|
|
- Form submission (Fetch API)
|
|
- Delete confirmation
|
|
- Inline delete buttons
|
|
|
|
### create.html
|
|
- Character counter update
|
|
- Form submission (Fetch API)
|
|
- Error display/dismiss
|
|
- Loading state toggle
|
|
- Redirect on success
|
|
|
|
### edit.html
|
|
- Same as create.html + delete modal
|
|
|
|
### map.html
|
|
- Leaflet map initialization
|
|
- Marker clustering
|
|
- Popup display
|
|
- Type filter update
|
|
- Marker click handlers
|
|
|
|
---
|
|
|
|
## Color Reference
|
|
|
|
### Type Badges
|
|
- **Branch** (Filial): `#0f4c75` - Deep Blue
|
|
- **Warehouse** (Lager): `#f39c12` - Orange
|
|
- **Service Center** (Servicecenter): `#2eb341` - Green
|
|
- **Client Site** (Kundesite): `#9b59b6` - Purple
|
|
|
|
### Status Badges
|
|
- **Active**: `#2eb341` (Green) - `bg-success`
|
|
- **Inactive**: `#6c757d` (Gray) - `bg-secondary`
|
|
|
|
### Actions
|
|
- **Primary**: `#0f4c75` (Blue) - `btn-primary`
|
|
- **Secondary**: `#6c757d` (Gray) - `btn-outline-secondary`
|
|
- **Danger**: `#e74c3c` (Red) - `btn-danger`
|
|
|
|
---
|
|
|
|
## Responsive Breakpoints
|
|
|
|
| Size | Bootstrap | Applies | Changes |
|
|
|------|-----------|---------|---------|
|
|
| Mobile | < 576px | Default | Full-width forms, stacked buttons |
|
|
| Tablet | >= 768px | `col-md-*` | 2-column forms, table layout |
|
|
| Desktop | >= 1024px | `col-lg-*` | Multi-column forms, sidebar options |
|
|
|
|
### list.html Responsive Changes
|
|
- < 768px: Hide "City" column, show only essential
|
|
- >= 768px: Show all table columns
|
|
|
|
### detail.html Responsive Changes
|
|
- < 768px: Stacked tabs, full-width modals
|
|
- >= 768px: Side-by-side cards, responsive modals
|
|
|
|
---
|
|
|
|
## Icons (Font Awesome / Bootstrap Icons)
|
|
|
|
```html
|
|
<i class="bi bi-plus-lg"></i> <!-- Plus -->
|
|
<i class="bi bi-pencil"></i> <!-- Edit -->
|
|
<i class="bi bi-trash"></i> <!-- Delete -->
|
|
<i class="bi bi-eye"></i> <!-- View -->
|
|
<i class="bi bi-arrow-left"></i> <!-- Back -->
|
|
<i class="bi bi-map-marker-alt"></i> <!-- Location -->
|
|
<i class="bi bi-phone"></i> <!-- Phone -->
|
|
<i class="bi bi-envelope"></i> <!-- Email -->
|
|
<i class="bi bi-clock"></i> <!-- Time -->
|
|
<i class="bi bi-chevron-left"></i> <!-- Prev -->
|
|
<i class="bi bi-chevron-right"></i> <!-- Next -->
|
|
<i class="bi bi-funnel"></i> <!-- Filter -->
|
|
<i class="bi bi-pin-map"></i> <!-- Location Pin -->
|
|
<i class="bi bi-check-lg"></i> <!-- Check -->
|
|
<i class="bi bi-hourglass-split"></i> <!-- Loading -->
|
|
```
|
|
|
|
---
|
|
|
|
## Form Validation
|
|
|
|
### HTML5 Validation
|
|
- `required` - Field must be filled
|
|
- `type="email"` - Email format validation
|
|
- `type="tel"` - Phone format
|
|
- `type="number"` - Numeric input
|
|
- `min="-90" max="90"` - Range validation
|
|
- `maxlength="500"` - Length limit
|
|
|
|
### Server-Side Validation
|
|
Expected from API:
|
|
```json
|
|
{
|
|
"detail": "Validation error message",
|
|
"status": 422
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Client-Side
|
|
- HTML5 validation prevents invalid submissions
|
|
- Fetch API error handling
|
|
- Try-catch for async operations
|
|
- User-friendly error messages in alert boxes
|
|
|
|
### API Errors
|
|
Expected format:
|
|
```json
|
|
{
|
|
"detail": "Location not found"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Mobile Optimization
|
|
|
|
- **Touch targets**: Minimum 44px height
|
|
- **Forms**: Full-width on mobile
|
|
- **Tables**: Convert to card view at 768px
|
|
- **Buttons**: Stacked vertically on mobile
|
|
- **Modals**: Full-screen on mobile
|
|
- **Maps**: Responsive container
|
|
|
|
---
|
|
|
|
## Dark Mode
|
|
|
|
Automatic via `data-bs-theme` attribute on `<html>`:
|
|
- Light mode: `data-bs-theme="light"`
|
|
- Dark mode: `data-bs-theme="dark"`
|
|
|
|
CSS variables automatically adjust colors.
|
|
|
|
---
|
|
|
|
## External Dependencies
|
|
|
|
### CSS
|
|
```html
|
|
<!-- Bootstrap 5 -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
|
|
<!-- Bootstrap Icons -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
|
|
<!-- Leaflet (map.html only) -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/MarkerCluster.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/MarkerCluster.Default.css" />
|
|
```
|
|
|
|
### JavaScript
|
|
```html
|
|
<!-- Bootstrap 5 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- Leaflet (map.html only) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/leaflet.markercluster@1.5.1/dist/leaflet.markercluster.js"></script>
|
|
```
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Opening a Modal
|
|
```javascript
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
|
modal.show();
|
|
modal.hide();
|
|
```
|
|
|
|
### Fetch API Call
|
|
```javascript
|
|
const response = await fetch('/api/v1/locations', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
// Success
|
|
} else {
|
|
const error = await response.json();
|
|
// Show error
|
|
}
|
|
```
|
|
|
|
### Character Counter
|
|
```javascript
|
|
document.getElementById('notes').addEventListener('input', function() {
|
|
document.getElementById('charCount').textContent = this.value.length;
|
|
});
|
|
```
|
|
|
|
### Bulk Delete
|
|
```javascript
|
|
const selectedIds = Array.from(checkboxes)
|
|
.filter(cb => cb.checked)
|
|
.map(cb => cb.value);
|
|
|
|
Promise.all(selectedIds.map(id =>
|
|
fetch(`/api/v1/locations/${id}`, { method: 'DELETE' })
|
|
))
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
- [ ] Form validation works
|
|
- [ ] Required fields enforced
|
|
- [ ] Pagination navigation works
|
|
- [ ] Filters persist across pages
|
|
- [ ] Bulk select/deselect works
|
|
- [ ] Individual delete confirmation
|
|
- [ ] Modal forms submit correctly
|
|
- [ ] Inline errors display
|
|
- [ ] Map renders with markers
|
|
- [ ] Map filter updates markers
|
|
- [ ] Responsive at 375px
|
|
- [ ] Responsive at 768px
|
|
- [ ] Responsive at 1024px
|
|
- [ ] Dark mode works
|
|
- [ ] No console errors
|
|
- [ ] API endpoints working
|
|
|
|
---
|
|
|
|
## Support & Troubleshooting
|
|
|
|
### Maps not showing
|
|
- Check: Leaflet CDN is loaded
|
|
- Check: Locations have latitude/longitude
|
|
- Check: Zoom level is 6 (default Denmark view)
|
|
|
|
### Forms not submitting
|
|
- Check: All required fields filled
|
|
- Check: API endpoint is correct
|
|
- Check: CSRF protection if enabled
|
|
|
|
### Modals not opening
|
|
- Check: Bootstrap JS is loaded
|
|
- Check: Modal ID matches button target
|
|
- Check: No console errors
|
|
|
|
### Styles not applying
|
|
- Check: Bootstrap 5 CSS loaded
|
|
- Check: CSS variables inherited from base.html
|
|
- Check: Dark mode toggle working
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.0/)
|
|
- [Leaflet.js Documentation](https://leafletjs.com/)
|
|
- [Jinja2 Template Documentation](https://jinja.palletsprojects.com/)
|
|
- [MDN Web Docs - HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)
|
|
- [MDN Web Docs - CSS](https://developer.mozilla.org/en-US/docs/Web/CSS)
|
|
- [MDN Web Docs - JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
|
|
|
---
|
|
|
|
## Template Files
|
|
|
|
All files located in: `/app/modules/locations/templates/`
|
|
|
|
**Ready for production deployment** ✅
|
|
|
|
Last updated: 31 January 2026
|