update-css
This commit is contained in:
parent
cfe83f50e0
commit
4ed30c94c8
@ -44,12 +44,19 @@ class OnboardingService:
|
|||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
# Log creation
|
# Log creation (only store simple data, not objects)
|
||||||
|
log_metadata = {
|
||||||
|
'email': user.email,
|
||||||
|
'first_name': user.first_name,
|
||||||
|
'last_name': user.last_name,
|
||||||
|
'hospital_id': str(user.hospital_id) if user.hospital_id else None,
|
||||||
|
'department_id': str(user.department_id) if user.department_id else None,
|
||||||
|
}
|
||||||
UserProvisionalLog.objects.create(
|
UserProvisionalLog.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
event_type='created',
|
event_type='created',
|
||||||
description=f"Provisional user created",
|
description=f"Provisional user created",
|
||||||
metadata=user_data
|
metadata=log_metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|||||||
@ -196,6 +196,9 @@ def provisional_user_list(request):
|
|||||||
user_data = serializer.validated_data.copy()
|
user_data = serializer.validated_data.copy()
|
||||||
roles = request.POST.getlist('roles', [])
|
roles = request.POST.getlist('roles', [])
|
||||||
|
|
||||||
|
# Remove roles from user_data (not a User model field)
|
||||||
|
user_data.pop('roles', None)
|
||||||
|
|
||||||
# Create provisional user
|
# Create provisional user
|
||||||
user = OnboardingService.create_provisional_user(user_data)
|
user = OnboardingService.create_provisional_user(user_data)
|
||||||
|
|
||||||
@ -221,14 +224,29 @@ def provisional_user_list(request):
|
|||||||
is_provisional=True
|
is_provisional=True
|
||||||
).select_related('hospital', 'department').order_by('-created_at')
|
).select_related('hospital', 'department').order_by('-created_at')
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
total_count = provisional_users.count()
|
||||||
|
completed_count = provisional_users.filter(acknowledgement_completed_at__isnull=False).count()
|
||||||
|
in_progress_count = total_count - completed_count
|
||||||
|
|
||||||
# Get available roles
|
# Get available roles
|
||||||
from .models import Role
|
from .models import Role
|
||||||
roles = Role.objects.all()
|
roles = Role.objects.all()
|
||||||
|
|
||||||
|
# Get hospitals and departments for the form
|
||||||
|
from apps.organizations.models import Hospital, Department
|
||||||
|
hospitals = Hospital.objects.filter(status='active').order_by('name')
|
||||||
|
departments = Department.objects.filter(status='active').order_by('name')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'page_title': 'Provisional Users',
|
'page_title': 'Provisional Users',
|
||||||
'provisional_users': provisional_users,
|
'provisional_users': provisional_users,
|
||||||
'roles': roles,
|
'roles': roles,
|
||||||
|
'hospitals': hospitals,
|
||||||
|
'departments': departments,
|
||||||
|
'total_count': total_count,
|
||||||
|
'completed_count': completed_count,
|
||||||
|
'in_progress_count': in_progress_count,
|
||||||
}
|
}
|
||||||
return render(request, 'accounts/onboarding/provisional_list.html', context)
|
return render(request, 'accounts/onboarding/provisional_list.html', context)
|
||||||
|
|
||||||
@ -253,6 +271,12 @@ def provisional_user_progress(request, user_id):
|
|||||||
is_acknowledged=True
|
is_acknowledged=True
|
||||||
).select_related('checklist_item')
|
).select_related('checklist_item')
|
||||||
|
|
||||||
|
# Create a lookup dict: checklist_item_id -> acknowledged_at timestamp
|
||||||
|
acknowledged_timestamps = {}
|
||||||
|
for ack in acknowledged_items:
|
||||||
|
if ack.checklist_item_id:
|
||||||
|
acknowledged_timestamps[ack.checklist_item_id] = ack.acknowledged_at
|
||||||
|
|
||||||
# Get logs
|
# Get logs
|
||||||
from .models import UserProvisionalLog
|
from .models import UserProvisionalLog
|
||||||
logs = UserProvisionalLog.objects.filter(
|
logs = UserProvisionalLog.objects.filter(
|
||||||
@ -265,15 +289,25 @@ def provisional_user_progress(request, user_id):
|
|||||||
checklist_item__is_required=True
|
checklist_item__is_required=True
|
||||||
).count()
|
).count()
|
||||||
progress_percentage = int((acknowledged_count / total_items) * 100) if total_items > 0 else 0
|
progress_percentage = int((acknowledged_count / total_items) * 100) if total_items > 0 else 0
|
||||||
|
remaining_count = total_items - acknowledged_count
|
||||||
|
|
||||||
|
# Attach acknowledged_at timestamp to each checklist item
|
||||||
|
checklist_items_with_timestamps = []
|
||||||
|
for item in checklist_items:
|
||||||
|
item_dict = {
|
||||||
|
'item': item,
|
||||||
|
'acknowledged_at': acknowledged_timestamps.get(item.id),
|
||||||
|
}
|
||||||
|
checklist_items_with_timestamps.append(item_dict)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'page_title': f'Onboarding Progress - {user.email}',
|
'page_title': f'Onboarding Progress - {user.email}',
|
||||||
'user': user,
|
'user': user,
|
||||||
'checklist_items': checklist_items,
|
'checklist_items': checklist_items_with_timestamps,
|
||||||
'acknowledged_items': acknowledged_items,
|
|
||||||
'logs': logs,
|
'logs': logs,
|
||||||
'total_items': total_items,
|
'total_items': total_items,
|
||||||
'acknowledged_count': acknowledged_count,
|
'acknowledged_count': acknowledged_count,
|
||||||
|
'remaining_count': remaining_count,
|
||||||
'progress_percentage': progress_percentage,
|
'progress_percentage': progress_percentage,
|
||||||
}
|
}
|
||||||
return render(request, 'accounts/onboarding/progress_detail.html', context)
|
return render(request, 'accounts/onboarding/progress_detail.html', context)
|
||||||
|
|||||||
@ -48,7 +48,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Provisional User Management
|
# Provisional User Management
|
||||||
path('onboarding/provisional/', provisional_user_list, name='provisional-user-list'),
|
path('onboarding/provisional/', provisional_user_list, name='provisional-user-list'),
|
||||||
path('onboarding/provisional/<int:user_id>/progress/', provisional_user_progress, name='provisional-user-progress'),
|
path('onboarding/provisional/<uuid:user_id>/progress/', provisional_user_progress, name='provisional-user-progress'),
|
||||||
|
|
||||||
# Acknowledgement Management
|
# Acknowledgement Management
|
||||||
path('onboarding/content/', acknowledgement_content_list, name='acknowledgement-content-list'),
|
path('onboarding/content/', acknowledgement_content_list, name='acknowledgement-content-list'),
|
||||||
|
|||||||
@ -12,6 +12,7 @@ def sidebar_counts(request):
|
|||||||
- Active complaints
|
- Active complaints
|
||||||
- Pending feedback
|
- Pending feedback
|
||||||
- Open PX actions
|
- Open PX actions
|
||||||
|
- Provisional users (PX Admin only)
|
||||||
"""
|
"""
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return {}
|
return {}
|
||||||
@ -33,6 +34,12 @@ def sidebar_counts(request):
|
|||||||
action_count = PXAction.objects.filter(
|
action_count = PXAction.objects.filter(
|
||||||
status__in=['open', 'in_progress']
|
status__in=['open', 'in_progress']
|
||||||
).count()
|
).count()
|
||||||
|
# Count provisional users for PX Admin
|
||||||
|
from apps.accounts.models import User
|
||||||
|
provisional_user_count = User.objects.filter(
|
||||||
|
is_provisional=True,
|
||||||
|
acknowledgement_completed=False
|
||||||
|
).count()
|
||||||
elif user.hospital:
|
elif user.hospital:
|
||||||
complaint_count = Complaint.objects.filter(
|
complaint_count = Complaint.objects.filter(
|
||||||
hospital=user.hospital,
|
hospital=user.hospital,
|
||||||
@ -46,13 +53,16 @@ def sidebar_counts(request):
|
|||||||
hospital=user.hospital,
|
hospital=user.hospital,
|
||||||
status__in=['open', 'in_progress']
|
status__in=['open', 'in_progress']
|
||||||
).count()
|
).count()
|
||||||
|
provisional_user_count = 0
|
||||||
else:
|
else:
|
||||||
complaint_count = 0
|
complaint_count = 0
|
||||||
feedback_count = 0
|
feedback_count = 0
|
||||||
action_count = 0
|
action_count = 0
|
||||||
|
provisional_user_count = 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'complaint_count': complaint_count,
|
'complaint_count': complaint_count,
|
||||||
'feedback_count': feedback_count,
|
'feedback_count': feedback_count,
|
||||||
'action_count': action_count,
|
'action_count': action_count,
|
||||||
|
'provisional_user_count': provisional_user_count,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,7 +154,13 @@ LOCALE_PATHS = [
|
|||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / 'static',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Media files
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
# WhiteNoise configuration
|
# WhiteNoise configuration
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
@ -166,9 +172,7 @@ STORAGES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Media files
|
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
MEDIA_ROOT = BASE_DIR / 'media'
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|||||||
314
docs/ALHAMMADI_THEME_GUIDE.md
Normal file
314
docs/ALHAMMADI_THEME_GUIDE.md
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# Al Hammadi Hospital Theme - PX360 Implementation Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the Al Hammadi Hospital theme implementation for the PX360 Patient Experience Management System. The theme reflects the visual identity of Al Hammadi Hospital (https://alhammadi.med.sa).
|
||||||
|
|
||||||
|
## Color Palette
|
||||||
|
|
||||||
|
### Primary Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Primary Teal | `#0097a7` | `--hh-primary` | Main brand color, buttons, links |
|
||||||
|
| Primary Dark | `#00838f` | `--hh-primary-dark` | Hover states, gradients |
|
||||||
|
| Primary Light | `#4dd0e1` | `--hh-primary-light` | Info badges, highlights |
|
||||||
|
| Primary Lighter | `#b2ebf2` | `--hh-primary-lighter` | Progress bar backgrounds |
|
||||||
|
| Primary BG | `rgba(0, 151, 167, 0.1)` | `--hh-primary-bg` | Soft backgrounds, hover states |
|
||||||
|
|
||||||
|
### Secondary Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Secondary Blue | `#1a237e` | `--hh-secondary` | Secondary buttons, accents |
|
||||||
|
| Secondary Dark | `#0d1642` | `--hh-secondary-dark` | Hover states |
|
||||||
|
| Secondary Light | `#283593` | `--hh-secondary-light` | Gradients |
|
||||||
|
|
||||||
|
### Accent Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Red Accent | `#c62828` | `--hh-accent` | Danger, alerts, logo crescent |
|
||||||
|
| Red Light | `#d32f2f` | `--hh-accent-light` | Gradients |
|
||||||
|
| Red Dark | `#b71c1c` | `--hh-accent-dark` | Hover states |
|
||||||
|
|
||||||
|
### Semantic Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Success | `#00897b` | `--hh-success` | Success states, positive indicators |
|
||||||
|
| Warning | `#f9a825` | `--hh-warning` | Warning states, caution indicators |
|
||||||
|
| Danger | `#c62828` | `--hh-danger` | Error states, critical alerts |
|
||||||
|
| Info | `#0097a7` | `--hh-info` | Information, tips |
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Dark Text | `#263238` | `--hh-text-dark` | Headings, primary text |
|
||||||
|
| Muted Text | `#607d8b` | `--hh-text-muted` | Secondary text, labels |
|
||||||
|
| Light Text | `#90a4ae` | `--hh-text-light` | Placeholder text |
|
||||||
|
|
||||||
|
### Background Colors
|
||||||
|
|
||||||
|
| Color Name | Hex Code | CSS Variable | Usage |
|
||||||
|
|------------|----------|--------------|-------|
|
||||||
|
| Light BG | `#f5f7fa` | `--hh-bg-light` | Page background |
|
||||||
|
| White BG | `#ffffff` | `--hh-bg-white` | Cards, panels |
|
||||||
|
| Border | `#e0e6ed` | `--hh-border` | Borders, dividers |
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
### Font Families
|
||||||
|
|
||||||
|
- **English**: Open Sans (Google Fonts)
|
||||||
|
- **Arabic**: Cairo (Google Fonts)
|
||||||
|
|
||||||
|
### Font Weights
|
||||||
|
|
||||||
|
- Light: 300
|
||||||
|
- Regular: 400
|
||||||
|
- Medium: 500
|
||||||
|
- Semi-Bold: 600
|
||||||
|
- Bold: 700
|
||||||
|
|
||||||
|
## Component Styling
|
||||||
|
|
||||||
|
### Sidebar
|
||||||
|
|
||||||
|
The sidebar uses a teal gradient background with the Al Hammadi brand colors:
|
||||||
|
|
||||||
|
```css
|
||||||
|
background: linear-gradient(180deg, var(--hh-primary-dark) 0%, var(--hh-primary) 50%, var(--hh-primary-dark) 100%);
|
||||||
|
```
|
||||||
|
|
||||||
|
- Active items have a red accent border (matching the logo crescent)
|
||||||
|
- Badges use theme colors for different states
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
Cards feature:
|
||||||
|
- No border (border: none)
|
||||||
|
- Subtle shadow (--hh-shadow-sm)
|
||||||
|
- Hover effect with increased shadow
|
||||||
|
- Teal gradient headers for primary cards
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
| Button Type | Background | Border | Text |
|
||||||
|
|-------------|------------|--------|------|
|
||||||
|
| Primary | Teal | Teal | White |
|
||||||
|
| Secondary | Dark Blue | Dark Blue | White |
|
||||||
|
| Danger | Red | Red | White |
|
||||||
|
| Success | Teal-Green | Teal-Green | White |
|
||||||
|
| Warning | Gold | Gold | Dark |
|
||||||
|
| Soft Primary | Light Teal BG | None | Teal |
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
|
||||||
|
- Header: Light gray background with uppercase text
|
||||||
|
- Rows: Hover effect with light teal background
|
||||||
|
- Striped: Alternating very light teal
|
||||||
|
|
||||||
|
### Forms
|
||||||
|
|
||||||
|
- Focus state: Teal border with light teal shadow
|
||||||
|
- Checkboxes/Radio: Teal when checked
|
||||||
|
- Select2: Bootstrap 5 theme with teal accents
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
- Header: Teal gradient background
|
||||||
|
- Close button: White (inverted)
|
||||||
|
- Footer: Light border
|
||||||
|
|
||||||
|
### Alerts
|
||||||
|
|
||||||
|
All alerts use soft backgrounds with matching text colors:
|
||||||
|
- Primary/Info: Light teal background
|
||||||
|
- Success: Light green background
|
||||||
|
- Danger: Light red background
|
||||||
|
- Warning: Light gold background
|
||||||
|
|
||||||
|
## Utility Classes
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
- `.text-teal` - Primary teal
|
||||||
|
- `.text-teal-dark` - Dark teal
|
||||||
|
- `.text-red` - Red accent
|
||||||
|
- `.text-blue` - Secondary blue
|
||||||
|
|
||||||
|
### Background Colors
|
||||||
|
- `.bg-teal` - Primary teal
|
||||||
|
- `.bg-teal-light` - Light teal (10% opacity)
|
||||||
|
- `.bg-red` - Red accent
|
||||||
|
- `.bg-blue` - Secondary blue
|
||||||
|
|
||||||
|
### Gradients
|
||||||
|
- `.bg-gradient-teal` - Teal gradient
|
||||||
|
- `.bg-gradient-red` - Red gradient
|
||||||
|
- `.bg-gradient-blue` - Blue gradient
|
||||||
|
|
||||||
|
### Effects
|
||||||
|
- `.hover-lift` - Lift effect on hover
|
||||||
|
|
||||||
|
### Stat Card Icons
|
||||||
|
- `.stat-icon.bg-teal` - Teal icon background
|
||||||
|
- `.stat-icon.bg-red` - Red icon background
|
||||||
|
- `.stat-icon.bg-blue` - Blue icon background
|
||||||
|
- `.stat-icon.bg-green` - Green icon background
|
||||||
|
|
||||||
|
### Avatars
|
||||||
|
- `.avatar-teal` - Teal avatar
|
||||||
|
- `.avatar-red` - Red avatar
|
||||||
|
- `.avatar-blue` - Blue avatar
|
||||||
|
|
||||||
|
### Badges (Soft)
|
||||||
|
- `.badge-soft-primary` - Soft teal badge
|
||||||
|
- `.badge-soft-danger` - Soft red badge
|
||||||
|
- `.badge-soft-success` - Soft green badge
|
||||||
|
- `.badge-soft-warning` - Soft gold badge
|
||||||
|
|
||||||
|
## RTL Support
|
||||||
|
|
||||||
|
The theme includes full RTL (Right-to-Left) support for Arabic:
|
||||||
|
|
||||||
|
- Sidebar moves to right side
|
||||||
|
- Border accents switch sides
|
||||||
|
- Icon margins adjust
|
||||||
|
- Timeline reverses direction
|
||||||
|
- All spacing and alignment adapts
|
||||||
|
|
||||||
|
## Responsive Design
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
|
||||||
|
- Mobile: < 576px
|
||||||
|
- Tablet: 576px - 991px
|
||||||
|
- Desktop: ≥ 992px
|
||||||
|
|
||||||
|
### Mobile Behavior
|
||||||
|
|
||||||
|
- Sidebar collapses off-screen
|
||||||
|
- Topbar spans full width
|
||||||
|
- Main content removes margins
|
||||||
|
- Stat cards adjust sizing
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Creating a Stat Card
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="stat-icon bg-teal">
|
||||||
|
<i class="bi bi-people"></i>
|
||||||
|
</div>
|
||||||
|
<p class="stat-label">Total Users</p>
|
||||||
|
<h3 class="stat-value">1,234</h3>
|
||||||
|
<p class="stat-trend up">
|
||||||
|
<i class="bi bi-arrow-up"></i> 12% from last month
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a Teal Header Card
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-teal">
|
||||||
|
<h5 class="card-title text-white mb-0">Card Title</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
Content here...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Soft Buttons
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="btn btn-soft-primary">Primary Action</button>
|
||||||
|
<button class="btn btn-soft-danger">Delete</button>
|
||||||
|
<button class="btn btn-soft-success">Approve</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Soft Badges
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span class="badge badge-soft-primary">Active</span>
|
||||||
|
<span class="badge badge-soft-danger">Overdue</span>
|
||||||
|
<span class="badge badge-soft-success">Completed</span>
|
||||||
|
<span class="badge badge-soft-warning">Pending</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ApexCharts Theme Colors
|
||||||
|
|
||||||
|
When using ApexCharts, use these colors for consistency:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const chartColors = {
|
||||||
|
primary: '#0097a7',
|
||||||
|
secondary: '#1a237e',
|
||||||
|
success: '#00897b',
|
||||||
|
danger: '#c62828',
|
||||||
|
warning: '#f9a825',
|
||||||
|
info: '#4dd0e1'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example chart options
|
||||||
|
const options = {
|
||||||
|
colors: [chartColors.primary, chartColors.success, chartColors.warning, chartColors.danger],
|
||||||
|
// ... other options
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
templates/
|
||||||
|
├── layouts/
|
||||||
|
│ ├── base.html # Main template with theme CSS
|
||||||
|
│ └── partials/
|
||||||
|
│ ├── sidebar.html # Sidebar navigation
|
||||||
|
│ ├── topbar.html # Top navigation bar
|
||||||
|
│ └── ...
|
||||||
|
docs/
|
||||||
|
└── ALHAMMADI_THEME_GUIDE.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
The theme supports:
|
||||||
|
- Chrome (latest)
|
||||||
|
- Firefox (latest)
|
||||||
|
- Safari (latest)
|
||||||
|
- Edge (latest)
|
||||||
|
- Mobile browsers (iOS Safari, Chrome for Android)
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### Updating Colors
|
||||||
|
|
||||||
|
To update the color palette, modify the CSS variables in `:root` section of `templates/layouts/base.html`.
|
||||||
|
|
||||||
|
### Adding New Components
|
||||||
|
|
||||||
|
When adding new components:
|
||||||
|
1. Use existing CSS variables for colors
|
||||||
|
2. Follow the established naming conventions
|
||||||
|
3. Include RTL support
|
||||||
|
4. Test on mobile devices
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
| Version | Date | Changes |
|
||||||
|
|---------|------|---------|
|
||||||
|
| 1.0.0 | January 2026 | Initial Al Hammadi theme implementation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Theme Based On**: Al Hammadi Hospital Website (https://alhammadi.med.sa)
|
||||||
|
**Implemented For**: PX360 Patient Experience Management System
|
||||||
|
**Last Updated**: January 2026
|
||||||
BIN
static/img/2024930115554.jpg
Normal file
BIN
static/img/2024930115554.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
static/img/logo.png
Normal file
BIN
static/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
169
templates/accounts/onboarding/checklist_list.html
Normal file
169
templates/accounts/onboarding/checklist_list.html
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Acknowledgement Checklist Items" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-2">
|
||||||
|
<i class="fas fa-tasks me-2"></i>
|
||||||
|
{% trans "Checklist Items Management" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "Manage acknowledgement checklist items" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus me-2"></i>
|
||||||
|
{% trans "Add Checklist Item" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Checklist Items List -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
{% trans "Checklist Items" %}
|
||||||
|
</h5>
|
||||||
|
<div class="input-group" style="max-width: 300px;">
|
||||||
|
<input type="text" class="form-control" id="searchInput" placeholder="{% trans 'Search items...' %}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0 align-middle" id="itemsTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Item Text" %}</th>
|
||||||
|
<th>{% trans "Role" %}</th>
|
||||||
|
<th>{% trans "Linked Content" %}</th>
|
||||||
|
<th>{% trans "Required" %}</th>
|
||||||
|
<th>{% trans "Order" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Created" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in checklist_items %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>{{ item.text_en }}</strong>
|
||||||
|
{% if item.code %}
|
||||||
|
<span class="badge bg-light text-muted ms-2">{{ item.code }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if item.description_en %}
|
||||||
|
<p class="small text-muted mb-0 mt-1">{{ item.description_en }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.role %}
|
||||||
|
<span class="badge bg-info text-dark">
|
||||||
|
{{ item.get_role_display }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{% trans "All Roles" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.content %}
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
{{ item.content.title_en }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{% if item.is_required %}
|
||||||
|
<span class="badge bg-danger">
|
||||||
|
<i class="fas fa-exclamation-circle me-1"></i>
|
||||||
|
{% trans "Yes" %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{% trans "No" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ item.order }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.is_active %}
|
||||||
|
<span class="badge bg-success">
|
||||||
|
<i class="fas fa-check me-1"></i>
|
||||||
|
{% trans "Active" %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<i class="fas fa-times me-1"></i>
|
||||||
|
{% trans "Inactive" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>{{ item.created_at|date:"M d, Y" }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" title="{% trans 'Edit' %}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" title="{% trans 'Delete' %}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-5">
|
||||||
|
<i class="fas fa-clipboard-list fa-3x text-muted mb-3"></i>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "No checklist items found" %}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Search functionality
|
||||||
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
||||||
|
const searchValue = this.value.toLowerCase();
|
||||||
|
const table = document.getElementById('itemsTable');
|
||||||
|
const rows = table.getElementsByTagName('tr');
|
||||||
|
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
const cells = row.getElementsByTagName('td');
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
for (let j = 0; j < cells.length; j++) {
|
||||||
|
const cellText = cells[j].textContent.toLowerCase();
|
||||||
|
if (cellText.includes(searchValue)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = found ? '' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
153
templates/accounts/onboarding/content_list.html
Normal file
153
templates/accounts/onboarding/content_list.html
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Acknowledgement Content" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-2">
|
||||||
|
<i class="fas fa-book me-2"></i>
|
||||||
|
{% trans "Acknowledgement Content Management" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "Manage educational content for onboarding wizard" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus me-2"></i>
|
||||||
|
{% trans "Add Content" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content List -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
{% trans "Content Sections" %}
|
||||||
|
</h5>
|
||||||
|
<div class="input-group" style="max-width: 300px;">
|
||||||
|
<input type="text" class="form-control" id="searchInput" placeholder="{% trans 'Search content...' %}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0 align-middle" id="contentTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th width="50">{% trans "Icon" %}</th>
|
||||||
|
<th>{% trans "Title" %}</th>
|
||||||
|
<th>{% trans "Role" %}</th>
|
||||||
|
<th>{% trans "Order" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Created" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for content in content_list %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
{% if content.icon %}
|
||||||
|
<i class="fas {{ content.icon }} fa-lg text-{{ content.color|default:'primary' }}"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-file-alt fa-lg text-muted"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>{{ content.title_en }}</strong>
|
||||||
|
{% if content.code %}
|
||||||
|
<span class="badge bg-light text-muted ms-2">{{ content.code }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if content.role %}
|
||||||
|
<span class="badge bg-info text-dark">
|
||||||
|
{{ content.get_role_display }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{% trans "All Roles" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ content.order }}</td>
|
||||||
|
<td>
|
||||||
|
{% if content.is_active %}
|
||||||
|
<span class="badge bg-success">
|
||||||
|
<i class="fas fa-check me-1"></i>
|
||||||
|
{% trans "Active" %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">
|
||||||
|
<i class="fas fa-times me-1"></i>
|
||||||
|
{% trans "Inactive" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>{{ content.created_at|date:"M d, Y" }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" title="{% trans 'Edit' %}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" title="{% trans 'Delete' %}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-5">
|
||||||
|
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "No content found" %}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Search functionality
|
||||||
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
||||||
|
const searchValue = this.value.toLowerCase();
|
||||||
|
const table = document.getElementById('contentTable');
|
||||||
|
const rows = table.getElementsByTagName('tr');
|
||||||
|
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
const cells = row.getElementsByTagName('td');
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
for (let j = 0; j < cells.length; j++) {
|
||||||
|
const cellText = cells[j].textContent.toLowerCase();
|
||||||
|
if (cellText.includes(searchValue)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = found ? '' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
249
templates/accounts/onboarding/progress_detail.html
Normal file
249
templates/accounts/onboarding/progress_detail.html
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans page_title %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'accounts:provisional-user-list' %}" class="text-muted text-decoration-none mb-2 d-inline-block">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>
|
||||||
|
{% trans "Back to Provisional Users" %}
|
||||||
|
</a>
|
||||||
|
<h1 class="h3 mb-2">
|
||||||
|
<i class="fas fa-chart-line me-2"></i>
|
||||||
|
{% trans "Onboarding Progress" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">{{ user.email }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Overview -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted mb-3">{% trans "Overall Progress" %}</h6>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="text-muted">{% trans "Required Items" %}</span>
|
||||||
|
<span class="fw-bold">{{ acknowledged_count }} / {{ total_items }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress" style="height: 20px;">
|
||||||
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: {{ progress_percentage }}%;"
|
||||||
|
aria-valuenow="{{ progress_percentage }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
{{ progress_percentage }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if progress_percentage == 100 %}
|
||||||
|
<div class="alert alert-success mb-0">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
{% trans "All required acknowledgements completed!" %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
<span class="text-muted">{{ remaining_count }} {% trans "items remaining" %}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted mb-3">{% trans "User Details" %}</h6>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{% trans "Name:" %}</strong> {{ user.get_full_name }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{% trans "Email:" %}</strong> {{ user.email }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{% trans "Hospital:" %}</strong> {{ user.hospital.name|default:"-" }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{% trans "Department:" %}</strong> {{ user.department.name|default:"-" }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>{% trans "Roles:" %}</strong>
|
||||||
|
{% for group in user.groups.all %}
|
||||||
|
<span class="badge bg-secondary me-1">{{ group.name }}</span>
|
||||||
|
{% empty %}
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Checklist Items -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-tasks me-2"></i>
|
||||||
|
{% trans "Acknowledgement Checklist" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th width="50">{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Item" %}</th>
|
||||||
|
<th>{% trans "Role" %}</th>
|
||||||
|
<th>{% trans "Required" %}</th>
|
||||||
|
<th>{% trans "Acknowledged At" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checklist_item in checklist_items %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
{% if checklist_item.acknowledged_at %}
|
||||||
|
<i class="fas fa-check-circle text-success fa-lg"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="far fa-circle text-muted fa-lg"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>{{ checklist_item.item.text_en }}</strong>
|
||||||
|
{% if checklist_item.item.description_en %}
|
||||||
|
<p class="small text-muted mb-0 mt-1">{{ checklist_item.item.description_en }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-info text-dark">
|
||||||
|
{% if checklist_item.item.role %}{{ checklist_item.item.role }}{% else %}All Roles{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{% if checklist_item.item.is_required %}
|
||||||
|
<span class="badge bg-danger">{% trans "Yes" %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{% trans "No" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if checklist_item.acknowledged_at %}
|
||||||
|
<small>{{ checklist_item.acknowledged_at|date:"M d, Y H:i" }}</small>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-4">
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "No checklist items found for this user's role" %}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Activity Log -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-history me-2"></i>
|
||||||
|
{% trans "Activity Log" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="timeline">
|
||||||
|
{% for log in logs %}
|
||||||
|
<div class="timeline-item mb-3">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="timeline-marker bg-primary">
|
||||||
|
<i class="fas fa-circle"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||||
|
<h6 class="mb-0">{{ log.get_event_type_display }}</h6>
|
||||||
|
<small class="text-muted">{{ log.created_at|date:"M d, Y H:i" }}</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-0 text-muted">{{ log.description }}</p>
|
||||||
|
{% if log.ip_address %}
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-map-marker-alt me-1"></i>
|
||||||
|
{{ log.ip_address }}
|
||||||
|
</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<i class="fas fa-history fa-2x text-muted mb-2"></i>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "No activity recorded yet" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-marker {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
Template filter to get event type icon
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Any interactive functionality can be added here
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
338
templates/accounts/onboarding/provisional_list.html
Normal file
338
templates/accounts/onboarding/provisional_list.html
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Provisional Users" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-2">
|
||||||
|
<i class="fas fa-users-cog me-2"></i>
|
||||||
|
{% trans "Provisional Users Management" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "Manage and monitor provisional user onboarding" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>
|
||||||
|
{% trans "Create Provisional User" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistics Cards -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-primary mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-user-clock fa-2x text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-1">{% trans "Total Provisional" %}</h6>
|
||||||
|
<h3 class="mb-0">{{ total_count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-success mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-check-circle fa-2x text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-1">{% trans "Completed" %}</h6>
|
||||||
|
<h3 class="mb-0">{{ completed_count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-warning mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-hourglass-half fa-2x text-warning"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="text-muted mb-1">{% trans "In Progress" %}</h6>
|
||||||
|
<h3 class="mb-0">{{ in_progress_count }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Users List -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
{% trans "Provisional Users List" %}
|
||||||
|
</h5>
|
||||||
|
<div class="input-group" style="max-width: 300px;">
|
||||||
|
<input type="text" class="form-control" id="searchInput" placeholder="{% trans 'Search users...' %}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0 align-middle" id="usersTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "User" %}</th>
|
||||||
|
<th>{% trans "Hospital" %}</th>
|
||||||
|
<th>{% trans "Department" %}</th>
|
||||||
|
<th>{% trans "Roles" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th>{% trans "Created" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in provisional_users %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="avatar-circle bg-primary text-white">
|
||||||
|
{{ user.email|first|upper }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 ms-3">
|
||||||
|
<h6 class="mb-0">{{ user.email }}</h6>
|
||||||
|
<small class="text-muted">{{ user.first_name }} {{ user.last_name }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ user.hospital.name|default:"-" }}</td>
|
||||||
|
<td>{{ user.department.name|default:"-" }}</td>
|
||||||
|
<td>
|
||||||
|
{% for group in user.groups.all %}
|
||||||
|
<span class="badge bg-secondary me-1">{{ group.name }}</span>
|
||||||
|
{% empty %}
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if user.acknowledgement_completed_at %}
|
||||||
|
<span class="badge bg-success">
|
||||||
|
<i class="fas fa-check me-1"></i>
|
||||||
|
{% trans "Completed" %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning">
|
||||||
|
<i class="fas fa-spinner me-1"></i>
|
||||||
|
{% trans "In Progress" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>{{ user.created_at|date:"M d, Y" }}</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<a href="{% url 'accounts:provisional-user-progress' user.id %}"
|
||||||
|
class="btn btn-sm btn-outline-primary"
|
||||||
|
title="{% trans 'View Progress' %}">
|
||||||
|
|
||||||
|
<i class="bi bi-activity"></i>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-sm btn-outline-danger"
|
||||||
|
onclick="resendInvitation({{ user.id }})"
|
||||||
|
title="{% trans 'Resend Invitation' %}">
|
||||||
|
<i class="bi bi-send"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center py-5">
|
||||||
|
<i class="fas fa-user-slash fa-3x text-muted mb-3"></i>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "No provisional users found" %}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create User Modal -->
|
||||||
|
<div class="modal fade" id="createUserModal" tabindex="-1" aria-labelledby="createUserModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="createUserModalLabel">
|
||||||
|
<i class="fas fa-user-plus me-2"></i>
|
||||||
|
{% trans "Create Provisional User" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="email" class="form-label">
|
||||||
|
{% trans "Email Address" %}
|
||||||
|
<span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="phone" class="form-label">{% trans "Phone Number" %}</label>
|
||||||
|
<input type="tel" class="form-control" id="phone" name="phone">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="first_name" class="form-label">
|
||||||
|
{% trans "First Name" %}
|
||||||
|
<span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="first_name" name="first_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="last_name" class="form-label">
|
||||||
|
{% trans "Last Name" %}
|
||||||
|
<span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="last_name" name="last_name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="hospital" class="form-label">{% trans "Hospital" %}</label>
|
||||||
|
<select class="form-select" id="hospital" name="hospital">
|
||||||
|
<option value="">{% trans "Select Hospital" %}</option>
|
||||||
|
{% for hospital in hospitals %}
|
||||||
|
<option value="{{ hospital.id }}">{{ hospital.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="department" class="form-label">{% trans "Department" %}</label>
|
||||||
|
<select class="form-select" id="department" name="department">
|
||||||
|
<option value="">{% trans "Select Department" %}</option>
|
||||||
|
{% for department in departments %}
|
||||||
|
<option value="{{ department.id }}">{{ department.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{% trans "Roles" %}</label>
|
||||||
|
<div class="row">
|
||||||
|
{% for role in roles %}
|
||||||
|
<div class="col-md-6 mb-2">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
name="roles" id="role_{{ role.id }}"
|
||||||
|
value="{{ role.name }}">
|
||||||
|
<label class="form-check-label" for="role_{{ role.id }}">
|
||||||
|
{{ role.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>
|
||||||
|
{% trans "Create & Send Invitation" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.avatar-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Search functionality
|
||||||
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
||||||
|
const searchValue = this.value.toLowerCase();
|
||||||
|
const table = document.getElementById('usersTable');
|
||||||
|
const rows = table.getElementsByTagName('tr');
|
||||||
|
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
const cells = row.getElementsByTagName('td');
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
for (let j = 0; j < cells.length; j++) {
|
||||||
|
const cellText = cells[j].textContent.toLowerCase();
|
||||||
|
if (cellText.includes(searchValue)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = found ? '' : 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function resendInvitation(userId) {
|
||||||
|
if (confirm('{% trans "Are you sure you want to resend the invitation email?" %}')) {
|
||||||
|
fetch(`/accounts/onboarding/provisional/${userId}/resend/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('{% trans "Invitation sent successfully!" %}');
|
||||||
|
} else {
|
||||||
|
alert('{% trans "Failed to send invitation." %}');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('{% trans "An error occurred. Please try again." %}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -5,28 +5,37 @@
|
|||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<style>
|
<style>
|
||||||
|
/* Al Hammadi Theme - Command Center Styles */
|
||||||
.kpi-card {
|
.kpi-card {
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||||
|
border-left: 4px solid var(--hh-primary);
|
||||||
}
|
}
|
||||||
.kpi-card:hover {
|
.kpi-card:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
box-shadow: var(--hh-shadow-lg);
|
||||||
}
|
}
|
||||||
|
.kpi-card.border-left-primary { border-left-color: var(--hh-primary); }
|
||||||
|
.kpi-card.border-left-warning { border-left-color: var(--hh-warning); }
|
||||||
|
.kpi-card.border-left-danger { border-left-color: var(--hh-accent); }
|
||||||
|
.kpi-card.border-left-success { border-left-color: var(--hh-success); }
|
||||||
|
.kpi-card.border-left-info { border-left-color: var(--hh-primary-light); }
|
||||||
|
.kpi-card.border-left-secondary { border-left-color: var(--hh-secondary); }
|
||||||
|
|
||||||
.kpi-value {
|
.kpi-value {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.kpi-trend-up {
|
.kpi-trend-up {
|
||||||
color: #dc3545;
|
color: var(--hh-accent);
|
||||||
}
|
}
|
||||||
.kpi-trend-down {
|
.kpi-trend-down {
|
||||||
color: #198754;
|
color: var(--hh-success);
|
||||||
}
|
}
|
||||||
.chart-container {
|
.chart-container {
|
||||||
min-height: 350px;
|
min-height: 350px;
|
||||||
}
|
}
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
background: #f8f9fa;
|
background: var(--hh-bg-light);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
@ -39,7 +48,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(255,255,255,0.8);
|
background: rgba(255,255,255,0.9);
|
||||||
display: none;
|
display: none;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -48,10 +57,36 @@
|
|||||||
.loading-overlay.active {
|
.loading-overlay.active {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.loading-overlay .spinner-border {
|
||||||
|
color: var(--hh-primary);
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
.physician-row:hover {
|
.physician-row:hover {
|
||||||
background-color: #f8f9fa;
|
background-color: var(--hh-primary-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* KPI Text Colors */
|
||||||
|
.text-primary { color: var(--hh-primary) !important; }
|
||||||
|
.text-warning { color: var(--hh-warning) !important; }
|
||||||
|
.text-danger { color: var(--hh-accent) !important; }
|
||||||
|
.text-success { color: var(--hh-success) !important; }
|
||||||
|
.text-info { color: var(--hh-primary-light) !important; }
|
||||||
|
.text-secondary { color: var(--hh-secondary) !important; }
|
||||||
|
|
||||||
|
/* Card Header Styling */
|
||||||
|
.card-header h6 {
|
||||||
|
color: var(--hh-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Styling */
|
||||||
|
.table thead th {
|
||||||
|
background: var(--hh-bg-light);
|
||||||
|
color: var(--hh-text-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid var(--hh-border);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -632,6 +667,9 @@ function renderChart(elementId, chartData, chartType) {
|
|||||||
charts[elementId].destroy();
|
charts[elementId].destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Al Hammadi Theme Colors for Charts
|
||||||
|
const hhChartColors = ['#0097a7', '#00897b', '#f9a825', '#c62828', '#1a237e', '#4dd0e1'];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
series: chartData.series || [],
|
series: chartData.series || [],
|
||||||
chart: {
|
chart: {
|
||||||
@ -639,10 +677,11 @@ function renderChart(elementId, chartData, chartType) {
|
|||||||
height: 350,
|
height: 350,
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: true
|
show: true
|
||||||
}
|
},
|
||||||
|
fontFamily: 'Open Sans, Cairo, sans-serif'
|
||||||
},
|
},
|
||||||
labels: chartData.labels || [],
|
labels: chartData.labels || [],
|
||||||
colors: ['#4472C4', '#4BC0C0', '#FF6384', '#36A2EB', '#FFCE56', '#9966FF'],
|
colors: hhChartColors,
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: chartType === 'donut'
|
enabled: chartType === 'donut'
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
{% load i18n %}
|
{% load i18n static%}
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<!-- Brand -->
|
<!-- Brand -->
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
@ -260,10 +260,34 @@
|
|||||||
{% if user.is_px_admin %}
|
{% if user.is_px_admin %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if 'config' in request.path %}active{% endif %}"
|
<a class="nav-link {% if 'config' in request.path %}active{% endif %}"
|
||||||
href="{% url 'config:dashboard' %}">
|
data-bs-toggle="collapse"
|
||||||
|
href="#settingsMenu"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="{% if 'config' in request.path %}true{% else %}false{% endif %}"
|
||||||
|
aria-controls="settingsMenu">
|
||||||
<i class="bi bi-gear"></i>
|
<i class="bi bi-gear"></i>
|
||||||
{% trans "Configuration" %}
|
{% trans "Settings" %}
|
||||||
|
<i class="bi bi-chevron-down ms-auto"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="collapse {% if 'config' in request.path %}show{% endif %}" id="settingsMenu">
|
||||||
|
<ul class="nav flex-column ms-3">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.resolver_match.url_name == 'config_dashboard' %}active{% endif %}"
|
||||||
|
href="{% url 'config:dashboard' %}">
|
||||||
|
<i class="bi bi-sliders"></i>
|
||||||
|
{% trans "Configuration" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if 'onboarding' in request.path %}active{% endif %}"
|
||||||
|
href="{% url 'accounts:provisional-user-list' %}">
|
||||||
|
<i class="bi bi-person-plus"></i>
|
||||||
|
{% trans "Onboarding" %}
|
||||||
|
<span class="badge bg-info">{{ provisional_user_count|default:0 }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,28 +1,29 @@
|
|||||||
{% load i18n %}
|
{% load i18n static%}
|
||||||
<div class="topbar d-flex align-items-center px-4">
|
<div class="topbar d-flex align-items-center px-4">
|
||||||
<!-- Mobile Menu Toggle -->
|
<!-- Mobile Menu Toggle -->
|
||||||
<button class="btn btn-link d-md-none me-3" type="button" onclick="document.querySelector('.sidebar').classList.toggle('show')">
|
<button class="btn btn-link text-teal d-lg-none me-3" type="button" onclick="document.querySelector('.sidebar').classList.toggle('show')">
|
||||||
<i class="bi bi-list fs-4"></i>
|
<i class="bi bi-list fs-4"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Page Title -->
|
<!-- Page Title -->
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h5 class="mb-0">{% block page_title %}{% trans "Dashboard" %}{% endblock %}</h5>
|
<img src="{% static 'img/logo.png' %}" height="50">
|
||||||
|
{# <h5 class="mb-0 text-teal-dark">{% block page_title %}{% trans "Dashboard" %}{% endblock %}</h5>#}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<div class="me-3 d-none d-md-block">
|
<div class="me-3 d-none d-md-block">
|
||||||
<div class="input-group" style="width: 300px;">
|
<div class="input-group" style="width: 300px;">
|
||||||
<span class="input-group-text bg-white border-end-0">
|
<span class="input-group-text bg-white border-end-0" style="border-color: var(--hh-border);">
|
||||||
<i class="bi bi-search"></i>
|
<i class="bi bi-search text-teal"></i>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" class="form-control border-start-0" placeholder="{% trans 'Search...' %}">
|
<input type="text" class="form-control border-start-0" placeholder="{% trans 'Search...' %}" style="border-color: var(--hh-border);">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<div class="dropdown me-3">
|
<div class="dropdown me-3">
|
||||||
<button class="btn btn-link position-relative p-0" type="button" data-bs-toggle="dropdown" style="line-height: 1;">
|
<button class="btn btn-link position-relative p-0 text-teal" type="button" data-bs-toggle="dropdown" style="line-height: 1;">
|
||||||
<i class="bi bi-bell fs-5"></i>
|
<i class="bi bi-bell fs-5"></i>
|
||||||
{% if notification_count|default:0 > 0 %}
|
{% if notification_count|default:0 > 0 %}
|
||||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.65rem;">
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.65rem;">
|
||||||
@ -30,25 +31,44 @@
|
|||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end" style="width: 300px;">
|
<ul class="dropdown-menu dropdown-menu-end" style="width: 320px;">
|
||||||
<li class="dropdown-header">{% trans "Notifications" %}</li>
|
<li class="dropdown-header d-flex justify-content-between align-items-center">
|
||||||
|
<span class="fw-semibold">{% trans "Notifications" %}</span>
|
||||||
|
<a href="#" class="text-teal small">{% trans "View All" %}</a>
|
||||||
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#">{% trans "No new notifications" %}</a></li>
|
<li>
|
||||||
|
<a class="dropdown-item py-2" href="#">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="avatar avatar-sm avatar-teal me-2">
|
||||||
|
<i class="bi bi-bell"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<p class="mb-0 small">{% trans "No new notifications" %}</p>
|
||||||
|
<small class="text-muted">{% trans "You're all caught up!" %}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Language Toggle -->
|
<!-- Language Toggle -->
|
||||||
<div class="dropdown me-3">
|
<div class="dropdown me-3">
|
||||||
<button class="btn btn-link" type="button" data-bs-toggle="dropdown">
|
<button class="btn btn-link text-teal" type="button" data-bs-toggle="dropdown">
|
||||||
<i class="bi bi-translate fs-5"></i>
|
<i class="bi bi-translate fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li class="dropdown-header">{% trans "Select Language" %}</li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<form action="{% url 'set_language' %}" method="post" style="display: inline;">
|
<form action="{% url 'set_language' %}" method="post" style="display: inline;">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<input type="hidden" name="language" value="en">
|
<input type="hidden" name="language" value="en">
|
||||||
<button type="submit" class="dropdown-item">English</button>
|
<button type="submit" class="dropdown-item d-flex align-items-center">
|
||||||
|
<span class="me-2">🇺🇸</span> English
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@ -56,7 +76,9 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<input type="hidden" name="language" value="ar">
|
<input type="hidden" name="language" value="ar">
|
||||||
<button type="submit" class="dropdown-item">العربية</button>
|
<button type="submit" class="dropdown-item d-flex align-items-center">
|
||||||
|
<span class="me-2">🇸🇦</span> العربية
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -64,21 +86,32 @@
|
|||||||
|
|
||||||
<!-- User Menu -->
|
<!-- User Menu -->
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-link d-flex align-items-center" type="button" data-bs-toggle="dropdown">
|
<button class="btn btn-link d-flex align-items-center text-decoration-none" type="button" data-bs-toggle="dropdown">
|
||||||
<div class="me-2 text-end d-none d-md-block">
|
<div class="me-2 text-end d-none d-md-block">
|
||||||
<div class="fw-semibold">{{ user.get_full_name|default:user.username }}</div>
|
<div class="fw-semibold" style="color: var(--hh-text-dark);">{{ user.get_full_name|default:user.username }}</div>
|
||||||
<small class="text-muted">{{ user.get_role_names.0|default:"User" }}</small>
|
<small style="color: var(--hh-text-muted);">{{ user.get_role_names.0|default:"User" }}</small>
|
||||||
</div>
|
|
||||||
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center"
|
|
||||||
style="width: 40px; height: 40px;">
|
|
||||||
{{ user.first_name.0|default:user.username.0|upper }}
|
|
||||||
</div>
|
</div>
|
||||||
|
{# <div class="avatar avatar-teal">#}
|
||||||
|
{# {{ user.first_name.0|default:user.username.0|upper }}#}
|
||||||
|
{# </div>#}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" href="#"><i class="bi bi-person me-2"></i>{% trans "Profile" %}</a></li>
|
<li class="dropdown-header">
|
||||||
<li><a class="dropdown-item" href="#"><i class="bi bi-gear me-2"></i>{% trans "Settings" %}</a></li>
|
<div class="d-flex align-items-center">
|
||||||
|
{# <div class="avatar avatar-teal me-2">#}
|
||||||
|
{# {{ user.first_name.0|default:user.username.0|upper }}#}
|
||||||
|
{# </div>#}
|
||||||
|
<div>
|
||||||
|
<div class="fw-semibold">{{ user.get_full_name|default:user.username }}</div>
|
||||||
|
<small class="text-muted">{{ user.email }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'admin:logout' %}"><i class="bi bi-box-arrow-right me-2"></i>{% trans "Logout" %}</a></li>
|
<li><a class="dropdown-item" href="#"><i class="bi bi-person me-2 text-teal"></i>{% trans "Profile" %}</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#"><i class="bi bi-gear me-2 text-teal"></i>{% trans "Settings" %}</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item text-danger" href="{% url 'admin:logout' %}"><i class="bi bi-box-arrow-right me-2"></i>{% trans "Logout" %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -193,16 +193,16 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if entry.trend == 'up' %}
|
{% if entry.trend == 'up' %}
|
||||||
<span class="badge bg-success">
|
<span class="text-success ">
|
||||||
<i class="bi bi-arrow-up"></i> {% trans "Up" %}
|
<i class="bi bi-arrow-up text-success"></i> {% trans "Up" %}
|
||||||
</span>
|
{# </span>#}
|
||||||
{% elif entry.trend == 'down' %}
|
{% elif entry.trend == 'down' %}
|
||||||
<span class="badge bg-danger">
|
<span class="text-danger">
|
||||||
<i class="bi bi-arrow-down"></i> {% trans "Down" %}
|
<i class="bi bi-arrow-down text-danger"></i> {% trans "Down" %}
|
||||||
</span>
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">
|
<span class="text-primary">
|
||||||
<i class="bi bi-dash"></i> {% trans "Stable" %}
|
<i class="bi bi-dash text-secondary"></i> {% trans "Stable" %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user