update-css
This commit is contained in:
parent
cfe83f50e0
commit
4ed30c94c8
@ -44,12 +44,19 @@ class OnboardingService:
|
||||
user.set_unusable_password()
|
||||
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(
|
||||
user=user,
|
||||
event_type='created',
|
||||
description=f"Provisional user created",
|
||||
metadata=user_data
|
||||
metadata=log_metadata
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@ -196,6 +196,9 @@ def provisional_user_list(request):
|
||||
user_data = serializer.validated_data.copy()
|
||||
roles = request.POST.getlist('roles', [])
|
||||
|
||||
# Remove roles from user_data (not a User model field)
|
||||
user_data.pop('roles', None)
|
||||
|
||||
# Create provisional user
|
||||
user = OnboardingService.create_provisional_user(user_data)
|
||||
|
||||
@ -221,14 +224,29 @@ def provisional_user_list(request):
|
||||
is_provisional=True
|
||||
).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
|
||||
from .models import Role
|
||||
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 = {
|
||||
'page_title': 'Provisional Users',
|
||||
'provisional_users': provisional_users,
|
||||
'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)
|
||||
|
||||
@ -253,6 +271,12 @@ def provisional_user_progress(request, user_id):
|
||||
is_acknowledged=True
|
||||
).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
|
||||
from .models import UserProvisionalLog
|
||||
logs = UserProvisionalLog.objects.filter(
|
||||
@ -265,15 +289,25 @@ def provisional_user_progress(request, user_id):
|
||||
checklist_item__is_required=True
|
||||
).count()
|
||||
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 = {
|
||||
'page_title': f'Onboarding Progress - {user.email}',
|
||||
'user': user,
|
||||
'checklist_items': checklist_items,
|
||||
'acknowledged_items': acknowledged_items,
|
||||
'checklist_items': checklist_items_with_timestamps,
|
||||
'logs': logs,
|
||||
'total_items': total_items,
|
||||
'acknowledged_count': acknowledged_count,
|
||||
'remaining_count': remaining_count,
|
||||
'progress_percentage': progress_percentage,
|
||||
}
|
||||
return render(request, 'accounts/onboarding/progress_detail.html', context)
|
||||
|
||||
@ -48,7 +48,7 @@ urlpatterns = [
|
||||
|
||||
# Provisional User Management
|
||||
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
|
||||
path('onboarding/content/', acknowledgement_content_list, name='acknowledgement-content-list'),
|
||||
|
||||
@ -12,6 +12,7 @@ def sidebar_counts(request):
|
||||
- Active complaints
|
||||
- Pending feedback
|
||||
- Open PX actions
|
||||
- Provisional users (PX Admin only)
|
||||
"""
|
||||
if not request.user.is_authenticated:
|
||||
return {}
|
||||
@ -33,6 +34,12 @@ def sidebar_counts(request):
|
||||
action_count = PXAction.objects.filter(
|
||||
status__in=['open', 'in_progress']
|
||||
).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:
|
||||
complaint_count = Complaint.objects.filter(
|
||||
hospital=user.hospital,
|
||||
@ -46,13 +53,16 @@ def sidebar_counts(request):
|
||||
hospital=user.hospital,
|
||||
status__in=['open', 'in_progress']
|
||||
).count()
|
||||
provisional_user_count = 0
|
||||
else:
|
||||
complaint_count = 0
|
||||
feedback_count = 0
|
||||
action_count = 0
|
||||
provisional_user_count = 0
|
||||
|
||||
return {
|
||||
'complaint_count': complaint_count,
|
||||
'feedback_count': feedback_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/
|
||||
STATIC_URL = '/static/'
|
||||
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
|
||||
STORAGES = {
|
||||
@ -166,9 +172,7 @@ STORAGES = {
|
||||
},
|
||||
}
|
||||
|
||||
# Media files
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# 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 %}
|
||||
<style>
|
||||
/* Al Hammadi Theme - Command Center Styles */
|
||||
.kpi-card {
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
border-left: 4px solid var(--hh-primary);
|
||||
}
|
||||
.kpi-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
transform: translateY(-3px);
|
||||
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 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.kpi-trend-up {
|
||||
color: #dc3545;
|
||||
color: var(--hh-accent);
|
||||
}
|
||||
.kpi-trend-down {
|
||||
color: #198754;
|
||||
color: var(--hh-success);
|
||||
}
|
||||
.chart-container {
|
||||
min-height: 350px;
|
||||
}
|
||||
.filter-panel {
|
||||
background: #f8f9fa;
|
||||
background: var(--hh-bg-light);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
@ -39,7 +48,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255,255,255,0.8);
|
||||
background: rgba(255,255,255,0.9);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -48,10 +57,36 @@
|
||||
.loading-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
.loading-overlay .spinner-border {
|
||||
color: var(--hh-primary);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
.physician-row:hover {
|
||||
background-color: #f8f9fa;
|
||||
background-color: var(--hh-primary-bg);
|
||||
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>
|
||||
{% endblock %}
|
||||
|
||||
@ -632,6 +667,9 @@ function renderChart(elementId, chartData, chartType) {
|
||||
charts[elementId].destroy();
|
||||
}
|
||||
|
||||
// Al Hammadi Theme Colors for Charts
|
||||
const hhChartColors = ['#0097a7', '#00897b', '#f9a825', '#c62828', '#1a237e', '#4dd0e1'];
|
||||
|
||||
const options = {
|
||||
series: chartData.series || [],
|
||||
chart: {
|
||||
@ -639,10 +677,11 @@ function renderChart(elementId, chartData, chartType) {
|
||||
height: 350,
|
||||
toolbar: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
fontFamily: 'Open Sans, Cairo, sans-serif'
|
||||
},
|
||||
labels: chartData.labels || [],
|
||||
colors: ['#4472C4', '#4BC0C0', '#FF6384', '#36A2EB', '#FFCE56', '#9966FF'],
|
||||
colors: hhChartColors,
|
||||
dataLabels: {
|
||||
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">
|
||||
<!-- Brand -->
|
||||
<div class="sidebar-brand">
|
||||
@ -260,10 +260,34 @@
|
||||
{% if user.is_px_admin %}
|
||||
<li class="nav-item">
|
||||
<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>
|
||||
{% trans "Configuration" %}
|
||||
{% trans "Settings" %}
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</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>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@ -1,28 +1,29 @@
|
||||
{% load i18n %}
|
||||
{% load i18n static%}
|
||||
<div class="topbar d-flex align-items-center px-4">
|
||||
<!-- 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>
|
||||
</button>
|
||||
|
||||
<!-- Page Title -->
|
||||
<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>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="me-3 d-none d-md-block">
|
||||
<div class="input-group" style="width: 300px;">
|
||||
<span class="input-group-text bg-white border-end-0">
|
||||
<i class="bi bi-search"></i>
|
||||
<span class="input-group-text bg-white border-end-0" style="border-color: var(--hh-border);">
|
||||
<i class="bi bi-search text-teal"></i>
|
||||
</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>
|
||||
|
||||
<!-- Notifications -->
|
||||
<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>
|
||||
{% 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;">
|
||||
@ -30,25 +31,44 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" style="width: 300px;">
|
||||
<li class="dropdown-header">{% trans "Notifications" %}</li>
|
||||
<ul class="dropdown-menu dropdown-menu-end" style="width: 320px;">
|
||||
<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><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>
|
||||
</div>
|
||||
|
||||
<!-- Language Toggle -->
|
||||
<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>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li class="dropdown-header">{% trans "Select Language" %}</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{% url 'set_language' %}" method="post" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<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>
|
||||
</li>
|
||||
<li>
|
||||
@ -56,7 +76,9 @@
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
@ -64,21 +86,32 @@
|
||||
|
||||
<!-- User Menu -->
|
||||
<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="fw-semibold">{{ user.get_full_name|default:user.username }}</div>
|
||||
<small class="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 class="fw-semibold" style="color: var(--hh-text-dark);">{{ user.get_full_name|default:user.username }}</div>
|
||||
<small style="color: var(--hh-text-muted);">{{ user.get_role_names.0|default:"User" }}</small>
|
||||
</div>
|
||||
{# <div class="avatar avatar-teal">#}
|
||||
{# {{ user.first_name.0|default:user.username.0|upper }}#}
|
||||
{# </div>#}
|
||||
</button>
|
||||
<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><a class="dropdown-item" href="#"><i class="bi bi-gear me-2"></i>{% trans "Settings" %}</a></li>
|
||||
<li class="dropdown-header">
|
||||
<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><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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -193,16 +193,16 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if entry.trend == 'up' %}
|
||||
<span class="badge bg-success">
|
||||
<i class="bi bi-arrow-up"></i> {% trans "Up" %}
|
||||
</span>
|
||||
<span class="text-success ">
|
||||
<i class="bi bi-arrow-up text-success"></i> {% trans "Up" %}
|
||||
{# </span>#}
|
||||
{% elif entry.trend == 'down' %}
|
||||
<span class="badge bg-danger">
|
||||
<i class="bi bi-arrow-down"></i> {% trans "Down" %}
|
||||
<span class="text-danger">
|
||||
<i class="bi bi-arrow-down text-danger"></i> {% trans "Down" %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">
|
||||
<i class="bi bi-dash"></i> {% trans "Stable" %}
|
||||
<span class="text-primary">
|
||||
<i class="bi bi-dash text-secondary"></i> {% trans "Stable" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user