update the css

This commit is contained in:
ismail 2025-11-18 14:05:28 +03:00
parent b61bb5222e
commit 8df7b2d42a
20 changed files with 2803 additions and 58 deletions

1
.gitignore vendored
View File

@ -53,7 +53,6 @@ htmlcov/
# Media and Static files (if served locally and not meant for version control)
media/
static/
# Deployment files
*.tar.gz

View File

@ -728,7 +728,7 @@ class InterviewScheduleForm(forms.ModelForm):
)
class Meta:
model = InterviewSchedule
model = InterviewSchedule
fields = [
'schedule_interview_type',
"applications",
@ -1544,8 +1544,7 @@ class CandidateEmailForm(forms.Form):
label=_('Select Candidates'), # Use a descriptive label
required=False
)
subject = forms.CharField(
max_length=200,
widget=forms.TextInput(attrs={
@ -1568,29 +1567,29 @@ class CandidateEmailForm(forms.Form):
required=True
)
def __init__(self, job, candidates, *args, **kwargs):
super().__init__(*args, **kwargs)
self.job = job
self.candidates=candidates
candidate_choices=[]
for candidate in candidates:
candidate_choices.append(
(f'candidate_{candidate.id}', f'{candidate.email}')
)
self.fields['to'].choices =candidate_choices
self.fields['to'].initial = [choice[0] for choice in candidate_choices]
self.fields['to'].initial = [choice[0] for choice in candidate_choices]
# Set initial message with candidate and meeting info
initial_message = self._get_initial_message()
if initial_message:
self.fields['message'].initial = initial_message
@ -1598,7 +1597,7 @@ class CandidateEmailForm(forms.Form):
"""Generate initial message with candidate and meeting information"""
candidate=self.candidates.first()
message_parts=[]
if candidate and candidate.stage == 'Applied':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
@ -1618,7 +1617,7 @@ class CandidateEmailForm(forms.Form):
f"We look forward to reviewing your results.",
f"Best regards, The KAAUH Hiring team"
]
elif candidate and candidate.stage == 'Interview':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
@ -1629,7 +1628,7 @@ class CandidateEmailForm(forms.Form):
f"We look forward to reviewing your results.",
f"Best regards, The KAAUH Hiring team"
]
elif candidate and candidate.stage == 'Offer':
message_parts = [
f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.",
@ -1648,9 +1647,9 @@ class CandidateEmailForm(forms.Form):
f"If you have any questions before your start date, please contact [Onboarding Contact].",
f"Best regards, The KAAUH Hiring team"
]
# # Add candidate information
# if self.candidate:
@ -1675,9 +1674,9 @@ class CandidateEmailForm(forms.Form):
def get_email_addresses(self):
"""Extract email addresses from selected recipients"""
email_addresses = []
candidates=self.cleaned_data.get('to',[])
if candidates:
for candidate in candidates:
if candidate.startswith('candidate_'):
@ -1691,7 +1690,7 @@ class CandidateEmailForm(forms.Form):
return list(set(email_addresses)) # Remove duplicates
def get_formatted_message(self):
"""Get the formatted message with optional additional information"""
@ -1871,7 +1870,7 @@ class InterviewEmailForm(forms.Form):
location = meeting.interview_location
# --- Data Preparation ---
# Safely access details through the related InterviewLocation object
if location and location.start_time:
formatted_date = location.start_time.strftime('%Y-%m-%d')
@ -1884,7 +1883,7 @@ class InterviewEmailForm(forms.Form):
formatted_time = "TBD"
duration = "N/A"
meeting_link = "Not Available"
job_title = job.title
agency_name = candidate.hiring_agency.name if candidate.belong_to_an_agency and candidate.hiring_agency else "Hiring Agency"
@ -1917,7 +1916,7 @@ Best regards,
KAAUH Hiring Team
"""
# ... (Messages for agency and participants remain the same, using the updated safe variables)
# --- 2. Agency Message (Professional and clear details) ---
agency_message = f"""
Dear {agency_name},
@ -1969,44 +1968,44 @@ class OnsiteLocationForm(forms.ModelForm):
class Meta:
model = OnsiteLocationDetails
# Include 'room_number' and update the field list
fields = ['topic', 'physical_address', 'room_number']
fields = ['topic', 'physical_address', 'room_number']
widgets = {
'topic': forms.TextInput(
attrs={'placeholder': 'Enter the Meeting Topic', 'class': 'form-control'}
),
'physical_address': forms.TextInput(
attrs={'placeholder': 'Physical address (e.g., street address)', 'class': 'form-control'}
),
'room_number': forms.TextInput(
attrs={'placeholder': 'Room Number/Name (Optional)', 'class': 'form-control'}
),
}
class OnsiteReshuduleForm(forms.ModelForm):
class Meta:
model = OnsiteLocationDetails
fields = ['topic', 'physical_address', 'room_number','start_time','duration','status']
fields = ['topic', 'physical_address', 'room_number','start_time','duration','status']
widgets = {
'topic': forms.TextInput(
attrs={'placeholder': 'Enter the Meeting Topic', 'class': 'form-control'}
),
'physical_address': forms.TextInput(
attrs={'placeholder': 'Physical address (e.g., street address)', 'class': 'form-control'}
),
'room_number': forms.TextInput(
attrs={'placeholder': 'Room Number/Name (Optional)', 'class': 'form-control'}
),
}
class OnsiteScheduleForm(forms.ModelForm):
# Add fields for the foreign keys required by ScheduledInterview
@ -2024,8 +2023,8 @@ class OnsiteScheduleForm(forms.ModelForm):
class Meta:
model = OnsiteLocationDetails
# Include all fields from OnsiteLocationDetails plus the new ones
fields = ['topic', 'physical_address', 'room_number', 'start_time', 'duration', 'status', 'application', 'job']
fields = ['topic', 'physical_address', 'room_number', 'start_time', 'duration', 'status', 'application', 'job']
widgets = {
'topic': forms.TextInput(
attrs={'placeholder': _('Enter the Meeting Topic'), 'class': 'form-control'}
@ -2036,7 +2035,7 @@ class OnsiteScheduleForm(forms.ModelForm):
'room_number': forms.TextInput(
attrs={'placeholder': _('Room Number/Name (Optional)'), 'class': 'form-control'}
),
# You should explicitly set widgets for start_time, duration, and status here
# You should explicitly set widgets for start_time, duration, and status here
# if they need Bootstrap classes, otherwise they will use default HTML inputs.
# Example:
'start_time': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),

721
static/css/main.css Normal file
View File

@ -0,0 +1,721 @@
/*
* KAAT-S Theme Styles (V2.0 - Consolidated Global, Nav, and Components)
* This file contains all variables, global layout styles, navigation, and component-specific styles.
*/
/* ---------------------------------- */
/* 1. UI Variables and Global Reset */
/* ---------------------------------- */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-light-bg: #f9fbfd;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
/* Primary Color Overrides */
.text-success { color: var(--kaauh-success) !important; }
.text-info { color: var(--kaauh-info) !important; }
.text-danger { color: var(--kaauh-danger) !important; }
.text-warning { color: var(--kaauh-warning) !important; }
.text-primary-theme { color: var(--kaauh-teal) !important; }
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
/* Global Layout Control */
.max-width-1600 {
max-width: 1600px;
margin-right: auto;
margin-left: auto;
padding-right: var(--bs-gutter-x, 0.75rem);
padding-left: var(--bs-gutter-x, 0.75rem);
}
/* Global Container Padding for main content */
.container-fluid.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
/* Main content minimum height */
main.container-fluid {
min-height: calc(100vh - 200px);
padding: 1.5rem 0;
}
/* ---------------------------------- */
/* 2. Navigation and Header */
/* ---------------------------------- */
/* Top Bar (Contact/Social) */
.top-bar {
background-color: white;
border-bottom: 1px solid var(--kaauh-border);
font-size: 0.825rem;
padding: 0.4rem 0;
}
.top-bar a { text-decoration: none; }
.top-bar .social-icons i {
color: var(--kaauh-teal);
transition: color 0.2s;
}
.top-bar .social-icons i:hover {
color: var(--kaauh-teal-dark);
}
.top-bar .contact-item {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.25rem 0.5rem;
}
.top-bar .logo-container img {
height: 60px;
object-fit: contain;
}
@media (max-width: 767.98px) {
.top-bar {
display: none;
}
}
/* Navbar */
.navbar-brand {
font-weight: 700;
letter-spacing: -0.5px;
font-size: 1.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.navbar-dark {
background-color: var(--kaauh-teal) !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Ensure the inner container of the navbar stretches to allow max-width-1600 */
.navbar-dark > .container {
max-width: 100%;
}
.nav-link {
font-weight: 500;
transition: all 0.2s ease;
padding: 0.5rem 0.75rem;
}
.nav-link:hover,
.nav-link.active {
color: white !important;
background: rgba(255,255,255,0.12) !important;
border-radius: 4px;
}
/* Dropdown */
.dropdown-menu {
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.98);
border: 1px solid var(--kaauh-border);
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
border-radius: 8px;
padding: 0.5rem 0;
min-width: 200px;
will-change: transform, opacity;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.dropdown-item {
padding: 0.5rem 1.25rem;
transition: background-color 0.15s;
}
.dropdown-item:hover {
background-color: var(--kaauh-light-bg);
color: var(--kaauh-teal-dark);
}
/* Language Toggle Button Style */
.language-toggle-btn {
color: white !important;
background: none !important;
border: none !important;
display: flex;
align-items: center;
gap: 0.3rem;
padding: 0.5rem 0.75rem !important;
font-weight: 500;
transition: all 0.2s ease;
}
.language-toggle-btn:hover {
background: rgba(255,255,255,0.12) !important;
border-radius: 4px;
}
/* Profile Avatar */
.profile-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--kaauh-teal);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.85rem;
transition: transform 0.1s ease;
}
.navbar-nav .dropdown-toggle:hover .profile-avatar {
transform: scale(1.05);
}
.navbar-nav .dropdown-toggle.p-0:hover {
background: none !important;
}
/* ---------------------------------- */
/* 3. Component Styles (Cards & Forms)*/
/* ---------------------------------- */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* NEW: Filter Controls Container Style */
.filter-controls {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
padding: 1.5rem; /* Consistent internal padding */
margin-bottom: 1.5rem; /* Space below filter */
}
/* Typography & Headers */
.page-header {
color: var(--kaauh-teal-dark);
font-weight: 700;
}
.section-header {
color: var(--kaauh-primary-text);
font-weight: 600;
border-bottom: 2px solid var(--kaauh-border);
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
label {
font-weight: 500;
color: var(--kaauh-primary-text);
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
/* Forms - Default Size */
.form-control, .form-select {
border-radius: 0.5rem;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
}
/* Forms - Compact Size (for modals/tables) */
.form-control-sm,
.form-select-sm,
.btn-sm {
padding: 0.25rem 0.5rem !important; /* Adjusted padding */
font-size: 0.8rem !important;
line-height: 1.25 !important; /* Standard small line height */
height: auto !important;
}
.form-select-sm {
padding: 0.25rem 2rem 0.25rem 0.5rem !important; /* Increased right padding for arrow */
font-size: 0.8rem !important;
line-height: 1.25 !important;
height: auto !important; /* Remove fixed height */
border-radius: 0.5rem;
border: 1px solid #ced4da;
}
/* Scrollable Multiple Select Fix */
.form-group select[multiple] {
max-height: 450px;
overflow-y: auto;
min-height: 250px;
padding: 0;
}
/* Break Times Section Styling (Schedule Interviews) */
.break-time-form {
background-color: #f8f9fa;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid var(--kaauh-border);
align-items: flex-end;
}
.note-box {
background-color: #fff3cd;
border-left: 5px solid var(--kaauh-warning);
padding: 1rem;
border-radius: 0.25rem;
font-size: 0.9rem;
margin-bottom: 1rem;
}
/* Tier Controls (Kept for consistency/future use) */
.tier-controls {
background-color: var(--kaauh-border);
border-radius: 0.75rem;
padding: 1.25rem;
margin-bottom: 2rem;
border: 1px solid var(--kaauh-border);
}
.tier-controls .form-row {
display: flex;
align-items: end;
gap: 1rem;
}
.tier-controls .form-group {
flex: 1;
margin-bottom: 0;
}
/* ---------------------------------- */
/* 4. Button Styles (Component Themed)*/
/* ---------------------------------- */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.btn-main-action.btn-sm { font-weight: 600 !important; }
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
.btn-bulk-pass {
background-color: var(--kaauh-success);
border-color: var(--kaauh-success);
color: white;
font-weight: 500;
}
.btn-bulk-pass:hover {
background-color: #1e7e34;
border-color: #1e7e34;
}
.btn-bulk-fail {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
font-weight: 500;
}
.btn-bulk-fail:hover {
background-color: #bd2130;
border-color: #bd2130;
}
.btn-apply { /* From Job Board table */
background: var(--kaauh-teal);
border: none;
color: white;
padding: 0.45rem 1rem;
font-weight: 600;
border-radius: 6px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-apply:hover {
background: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
/* ---------------------------------- */
/* 5. Table & Footer Styles */
/* ---------------------------------- */
.candidate-table {
table-layout: fixed;
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: white;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.candidate-table thead {
background-color: var(--kaauh-border);
}
.candidate-table th {
padding: 0.75rem;
font-weight: 600;
color: var(--kaauh-teal-dark);
border-bottom: 2px solid var(--kaauh-teal);
font-size: 0.9rem;
vertical-align: middle;
}
.candidate-table td {
padding: 0.75rem;
border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle;
font-size: 0.9rem;
}
.candidate-table tbody tr:hover {
background-color: #f1f3f4;
}
.candidate-name {
font-weight: 600;
color: var(--kaauh-primary-text);
}
.candidate-details {
font-size: 0.8rem;
color: #6c757d;
}
/* Job Table Specific Styles */
.job-table-wrapper {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0,0,0,0.06);
margin-bottom: 2rem;
}
.job-table thead th {
background: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 1rem;
text-align: center;
}
.job-table td {
padding: 1rem;
vertical-align: middle;
text-align: center;
}
.job-table tr:hover td {
background-color: rgba(0, 99, 110, 0.03);
}
/* Table Responsiveness */
@media (max-width: 575.98px) {
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.job-table th,
.job-table td {
white-space: nowrap;
font-size: 0.875rem;
}
}
/* Bulk Action Bar (Interview Management) */
.bulk-action-bar {
display: flex;
align-items: center;
gap: 0.5rem;
padding-bottom: 0.75rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--kaauh-border);
}
.action-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Badges (Adapted for Interview Tiers/Status) */
.ai-score-badge { /* Used as an all-purpose secondary badge */
background-color: var(--kaauh-teal-dark) !important;
color: white;
font-weight: 700;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-size: 0.8rem;
}
.tier-badge { /* Used for Tier labels */
font-size: 0.75rem;
padding: 0.125rem 0.5rem;
border-radius: 0.5rem;
font-weight: 600;
margin-left: 0.5rem;
display: inline-block;
}
.tier-1-badge { background-color: var(--kaauh-success); color: white; }
.tier-2-badge { background-color: var(--kaauh-warning); color: #856404; }
.tier-3-badge { background-color: #d1ecf1; color: #0c5460; }
.cd_interview{ color: #00636e; }
/* NEW: Application Stage Badges */
.stage-badge {
font-size: 0.8rem;
padding: 0.2em 0.6em;
border-radius: 0.4rem;
font-weight: 600;
display: inline-block;
margin-top: 0.25rem;
}
.stage-Application { background-color: #e9ecef; color: #495057; }
.stage-Screening { background-color: #d1ecf1; color: #0c5460; }
.stage-Exam { background-color: #cce5ff; color: #004085; }
.stage-Interview { background-color: #fff3cd; color: #856404; }
.stage-Offer { background-color: #d4edda; color: #155724; }
/* NEW: Applicant Status Badges */
.status-badge {
font-size: 0.8rem;
padding: 0.2em 0.6em;
border-radius: 0.4rem;
font-weight: 500;
display: inline-block;
}
.bg-candidate { background-color: var(--kaauh-teal-dark); color: white; }
.bg-applicant { background-color: #f8f9fa; color: #495057; border: 1px solid #ced4da; }
/* Table Column Width Fixes */
.candidate-table th:nth-child(1) { width: 40px; }
.candidate-table th:nth-child(4) { width: 15%; }
.candidate-table th:nth-child(5) { width: 80px; }
.candidate-table th:nth-child(6) { width: 180px; }
/* Footer & Alerts */
.footer {
background: var(--kaauh-light-bg);
padding: 1.5rem 0;
border-top: 1px solid var(--kaauh-border);
font-size: 0.9rem;
color: #555;
}
.alert {
border: none;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
/* ---------------------------------- */
/* 6. RTL Support */
/* ---------------------------------- */
html[dir="rtl"] {
text-align: right;
direction: rtl;
}
html[dir="rtl"] .navbar-brand {
flex-direction: row-reverse;
}
html[dir="rtl"] .dropdown-menu {
right: auto;
left: 0;
}
/* RTL Spacing adjustments */
html[dir="rtl"] .me-3 { margin-right: 0 !important; margin-left: 1rem !important; }
html[dir="rtl"] .ms-3 { margin-left: 0 !important; margin-right: 1rem !important; }
html[dir="rtl"] .me-2 { margin-right: 0 !important; margin-left: 0.5rem !important; }
html[dir="rtl"] .ms-2 { margin-left: 0 !important; margin-right: 0.5rem !important; }
html[dir="rtl"] .ms-auto { margin-left: 0 !important; margin-right: auto !important; }
html[dir="rtl"] .me-auto { margin-right: 0 !important; margin-left: auto !important; }
/* ================================================= */
/* 1. THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
.text-kaauh-primary { color: var(--kaauh-primary-text); } /* Custom class for primary text color if needed */
/* ---------------------------------- */
/* 2. Button Styles */
/* ---------------------------------- */
/* Main Action Button Style (Used for Download Resume) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
.btn-outline-primary:hover {
background-color: var(--kaauh-teal);
color: white;
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* ---------------------------------- */
/* 3. Card/Modal Styles */
/* ---------------------------------- */
/* Card enhancements */
.kaauh-card, .card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Candidate Header Card (The teal header) */
.candidate-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.candidate-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.candidate-header-card .badge {
font-size: 0.9rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
}
/* ---------------------------------- */
/* 4. Tab Navigation Styles (Candidate Detail View) */
/* ---------------------------------- */
/* Left Column Tabs (Main Content Tabs) */
.main-tabs {
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
padding: 0 1.25rem;
}
.main-tabs .nav-link {
border: none;
border-bottom: 3px solid transparent;
color: var(--kaauh-primary-text);
font-weight: 500;
padding: 0.75rem 1rem;
margin-right: 0.5rem;
transition: all 0.2s;
}
.main-tabs .nav-link:hover {
color: var(--kaauh-teal);
}
.main-tabs .nav-link.active {
color: var(--kaauh-teal-dark) !important;
background-color: white !important;
border-bottom: 3px solid var(--kaauh-teal);
font-weight: 600;
}
/* Right Column Card (General styling for tab content if needed) */
.right-column-card .tab-content {
padding: 1.5rem 1.25rem;
background-color: white;
}
/* ---------------------------------- */
/* 5. Vertical Timeline Styling */
/* ---------------------------------- */
/* Highlight box for the current stage */
.current-stage {
border: 1px solid var(--kaauh-border);
background-color: #f0f8ff; /* Light, subtle blue background */
}
.current-stage .text-primary {
color: var(--kaauh-teal) !important;
}
.timeline {
position: relative;
padding-left: 2rem;
}
.timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 1.25rem;
width: 2px;
background-color: var(--kaauh-border);
}
.timeline-item {
position: relative;
margin-bottom: 2rem;
padding-left: 1.5rem;
}
.timeline-icon {
position: absolute;
left: 0;
top: 0;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.8rem;
z-index: 10;
border: 4px solid white;
}
.timeline-item:last-child {
margin-bottom: 0;
}
/* Custom Timeline Background Classes for Stages (Using Bootstrap color palette) */
.timeline-bg-applied { background-color: var(--kaauh-teal) !important; }
.timeline-bg-exam { background-color: #17a2b8 !important; }
.timeline-bg-interview { background-color: #ffc107 !important; }
.timeline-bg-offer { background-color: #28a745 !important; }
.timeline-bg-rejected { background-color: #dc3545 !important; }
.loading {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}

786
static/css/messages.css Normal file
View File

@ -0,0 +1,786 @@
/* Unified Messaging System Styles */
:root {
--primary-color: #00636e;
--secondary-color: #f8f9fa;
--light-bg: #f8f9fa;
--border-color: #dee2e6;
--text-color: #333;
--text-muted: #6c757d;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #17a2b8;
}
/* Main Layout */
.unified-messages {
display: flex;
height: calc(100vh - 120px);
background: white;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
/* Sidebar */
.messages-sidebar {
width: 350px;
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
background: var(--light-bg);
}
.sidebar-header {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
background: white;
}
.sidebar-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab-btn {
padding: 0.5rem 1rem;
border: none;
background: transparent;
color: var(--text-muted);
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
font-weight: 500;
}
.tab-btn.active {
background: var(--primary-color);
color: white;
}
.tab-btn:hover:not(.active) {
background: var(--border-color);
}
.search-box {
position: relative;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
font-size: 0.875rem;
transition: border-color 0.2s ease;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
}
.search-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.compose-btn {
width: 100%;
padding: 0.75rem;
background: var(--primary-color);
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.compose-btn:hover {
background: #004d57;
}
/* Conversation List */
.conversation-list {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.conversation-item {
display: flex;
align-items: center;
padding: 0.75rem;
border-radius: 0.5rem;
cursor: pointer;
transition: background-color 0.2s ease;
margin-bottom: 0.25rem;
border: 1px solid transparent;
}
.conversation-item:hover {
background: white;
border-color: var(--border-color);
}
.conversation-item.active {
background: white;
border-color: var(--primary-color);
box-shadow: 0 2px 8px rgba(0, 99, 110, 0.1);
}
.conversation-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1rem;
margin-right: 0.75rem;
flex-shrink: 0;
position: relative;
}
.online-indicator {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
background: var(--success-color);
border: 2px solid white;
border-radius: 50%;
}
.conversation-info {
flex: 1;
min-width: 0;
}
.conversation-name {
font-weight: 600;
color: var(--text-color);
margin-bottom: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conversation-preview {
font-size: 0.875rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conversation-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.25rem;
}
.conversation-time {
font-size: 0.75rem;
color: var(--text-muted);
}
.unread-badge {
background: var(--primary-color);
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
}
/* Conversation Detail */
.conversation-detail {
flex: 1;
display: flex;
flex-direction: column;
}
.conversation-detail-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
background: white;
display: flex;
align-items: center;
justify-content: space-between;
}
.conversation-detail-info {
display: flex;
align-items: center;
}
.conversation-detail-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 0.75rem;
}
.conversation-detail-name {
font-weight: 600;
color: var(--text-color);
margin-bottom: 0.25rem;
}
.conversation-detail-status {
font-size: 0.875rem;
color: var(--text-muted);
}
.conversation-detail-actions {
display: flex;
gap: 0.5rem;
}
.detail-action-btn {
width: 36px;
height: 36px;
border: none;
background: transparent;
color: var(--text-muted);
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.detail-action-btn:hover {
background: var(--light-bg);
color: var(--primary-color);
}
/* Messages Container */
.messages-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: var(--light-bg);
}
.message {
display: flex;
align-items: flex-start;
margin-bottom: 1rem;
padding: 0 1rem;
}
.message.sent {
flex-direction: row-reverse;
}
.message.received {
flex-direction: row;
}
.message.reply {
margin-left: 2rem;
border-left: 2px solid var(--border-color);
padding-left: 1rem;
background: rgba(0, 99, 110, 0.02);
border-radius: 0.5rem;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
margin: 0 0.75rem;
flex-shrink: 0;
}
.message-avatar.reply-avatar {
width: 32px;
height: 32px;
font-size: 0.75rem;
margin: 0 0.5rem;
}
.message-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 70%;
}
.message.sent .message-content {
align-items: flex-end;
}
.message.received .message-content {
align-items: flex-start;
}
.message-bubble {
background: var(--light-bg);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 0.75rem 1rem;
margin-bottom: 0.25rem;
position: relative;
word-wrap: break-word;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.message-bubble.reply-bubble {
background: white;
border-radius: 0.75rem;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
.message.sent .message-bubble {
background: var(--primary-color);
color: white;
border-bottom-right-radius: 0.25rem;
}
.message.received .message-bubble {
background: white;
color: var(--text-color);
border-bottom-left-radius: 0.25rem;
}
.thread-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
padding: 0.25rem 0.5rem;
background: rgba(0, 99, 110, 0.1);
border-radius: 0.25rem;
font-size: 0.75rem;
color: var(--primary-color);
}
.reply-info {
display: flex;
align-items: center;
gap: 0.25rem;
margin-bottom: 0.25rem;
font-size: 0.75rem;
color: var(--text-muted);
}
.message-text {
line-height: 1.5;
margin-bottom: 0.25rem;
}
.message-time {
font-size: 0.75rem;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Reply Area */
.message-reply-area {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border-color);
background: white;
}
.reply-form {
display: flex;
align-items: flex-end;
gap: 0.75rem;
}
.reply-input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: 1.5rem;
resize: none;
font-family: inherit;
font-size: 0.875rem;
line-height: 1.5;
max-height: 120px;
min-height: 44px;
transition: border-color 0.2s ease;
}
.reply-input:focus {
outline: none;
border-color: var(--primary-color);
}
.reply-tools {
display: flex;
align-items: center;
gap: 0.5rem;
}
.tool-btn {
width: 36px;
height: 36px;
border: none;
background: transparent;
color: var(--text-muted);
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.tool-btn:hover {
background: var(--light-bg);
color: var(--primary-color);
}
.send-btn {
width: 36px;
height: 36px;
border: none;
background: var(--primary-color);
color: white;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.send-btn:hover:not(:disabled) {
background: #004d57;
}
.send-btn:disabled {
background: var(--text-muted);
cursor: not-allowed;
}
/* Empty States */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-muted);
text-align: center;
padding: 2rem;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-state h3 {
margin-bottom: 0.5rem;
color: var(--text-color);
}
.empty-state p {
font-size: 0.875rem;
}
/* Compose Modal */
.compose-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.compose-modal-content {
background: white;
border-radius: 0.5rem;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.compose-modal-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.compose-modal-body {
padding: 1.5rem;
}
.compose-form-group {
margin-bottom: 1rem;
}
.compose-form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--text-color);
}
.compose-form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: 0.5rem;
font-size: 0.875rem;
transition: border-color 0.2s ease;
}
.compose-form-control:focus {
outline: none;
border-color: var(--primary-color);
}
.compose-form-textarea {
resize: vertical;
min-height: 120px;
font-family: inherit;
line-height: 1.5;
}
.compose-modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 0.5rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-primary:hover {
background: #004d57;
}
.btn-secondary {
background: var(--light-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--border-color);
}
/* Responsive Design */
@media (max-width: 768px) {
.unified-messages {
height: calc(100vh - 60px);
}
.messages-sidebar {
width: 100%;
position: absolute;
z-index: 10;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.messages-sidebar.show {
transform: translateX(0);
}
.conversation-detail {
width: 100%;
}
.message-content {
max-width: 85%;
}
.compose-modal-content {
width: 95%;
margin: 1rem;
}
}
@media (max-width: 480px) {
.conversation-item {
padding: 0.5rem;
}
.conversation-avatar {
width: 40px;
height: 40px;
font-size: 0.875rem;
}
.message {
padding: 0 0.5rem;
}
.message-avatar {
width: 32px;
height: 32px;
font-size: 0.75rem;
margin: 0 0.5rem;
}
.message-content {
max-width: 90%;
}
.message-bubble {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
}
/* Animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message {
animation: slideIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.conversation-item {
animation: fadeIn 0.2s ease;
}
/* Loading States */
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--text-muted);
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid var(--border-color);
border-top: 2px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Notification Styles */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
color: white;
font-weight: 500;
z-index: 1001;
animation: slideInRight 0.3s ease;
max-width: 300px;
}
.notification.success {
background: var(--success-color);
}
.notification.error {
background: var(--danger-color);
}
.notification.info {
background: var(--info-color);
}
.notification.warning {
background: var(--warning-color);
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}

View File

@ -0,0 +1,162 @@
/* UI Variables for the KAAT-S Theme */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-gray: #6c757d;
/* Status Colors for alerts/messages */
--kaauh-success: var(--kaauh-teal);
--kaauh-danger: #dc3545;
--kaauh-info: #17a2b8;
}
/* CONTAINER AND CARD STYLING */
.container {
padding: 2rem 1rem;
}
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
max-width: 600px;
margin: 0 auto; /* Center the card */
padding: 1.5rem;
}
/* HEADER STYLING (The section outside the card) */
.header {
text-align: center;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2rem;
color: var(--kaauh-teal-dark);
font-weight: 700;
margin-bottom: 0.25rem;
}
.header p {
color: var(--kaauh-gray);
font-size: 1rem;
}
/* CARD TITLE STYLING */
.card-title {
font-size: 1.25rem;
color: var(--kaauh-teal-dark);
font-weight: 600;
border-bottom: 1px solid var(--kaauh-border);
padding-bottom: 0.75rem;
margin-bottom: 1.5rem;
}
/* FORM STYLING */
.form-row {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
font-weight: 600;
color: var(--kaauh-gray);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.form-input {
display: block;
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
line-height: 1.5;
color: var(--kaauh-primary-text);
background-color: #fff;
background-clip: padding-box;
border: 1px solid var(--kaauh-border);
border-radius: 0.5rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-input:focus {
border-color: var(--kaauh-teal);
outline: 0;
box-shadow: 0 0 0 0.1rem rgba(0, 99, 110, 0.25);
}
input[type="datetime-local"] {
font-family: inherit;
}
/* MESSAGES/ALERTS STYLING */
.messages {
max-width: 600px;
margin: 0 auto 1.5rem auto;
}
.alert {
padding: 1rem;
border-radius: 0.5rem;
margin-bottom: 0.5rem;
font-weight: 500;
}
.alert-success {
color: white;
background-color: var(--kaauh-success);
border-color: var(--kaauh-success);
}
.alert-danger {
color: white;
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
}
.alert-info {
color: white;
background-color: var(--kaauh-info);
border-color: var(--kaauh-info);
}
/* BUTTON STYLING */
.actions {
margin-top: 1.5rem;
display: flex;
gap: 1rem;
}
.btn-base {
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-align: center;
vertical-align: middle;
cursor: pointer;
user-select: none;
padding: 0.5rem 1rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.5rem;
font-weight: 600;
border: 1px solid transparent;
transition: all 0.2s ease;
text-decoration: none;
}
/* Primary Action Button (Update) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
color: white;
}
/* Secondary Button (Cancel) */
.btn-kaats-outline-secondary {
color: var(--kaauh-secondary);
border-color: var(--kaauh-secondary);
background-color: transparent;
}
.btn-kaats-outline-secondary:hover {
background-color: var(--kaauh-secondary);
color: white;
border-color: var(--kaauh-secondary);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
static/image/kaauh.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
static/image/kaauh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,120 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.425 132.744" style="version:1">
<switch>
<foreignObject width="1" height="1" x="0" y="0" requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"/>
<g>
<path fill="#ADA9AE" d="M141.468 75.372v0.145c0 9.575-6.977 16.611-18.315 16.611 -9.157 0-15.48-3.627-19.84-8.777l7.704-7.327c3.488 3.918 7.122 6.093 12.282 6.093 4.215 0 7.195-2.393 7.195-6.165 -0.056-0.1 0.822-6.6-9.957-6.6h-4.651l-1.745-7.11 16.32-16.385 -6.54 4.2h-17.628v-9.574h34.375v8.414l-12.863 12.258C134.709 62.316 141.468 65.942 141.468 75.372M109.563 17.265c0-4.597 3.302-8.297 7.936-8.297h5.222l-1.367 0.681c0.062 12.6-0.25 14.187 0.556 16.029l-4.353-0.001c-4.634 0-7.994-3.758-7.994-8.354V17.265zM117.499 27.788h4.637v-1.667c0.338 0.586 0.793 1.041 1.464 1.359 1.475 0.7 0.403 0.26 42.703 0.39V6.858h-2.231v18.785c0 0-38.678 0.149-39.413-0.181 -0.638-0.287-0.87-0.943-0.976-1.642 -0.169-1.135-0.066-1.156-0.097-16.962h-6.029c-6.169 0-10.369 4.885-10.369 10.465v0.058C107.188 22.96 111.33 27.788 117.499 27.788M182.746 4.63h-4.888c0.106-1.616-0.642-4.63 2.651-4.63h1.574v1.552c-1.524 0.113-2.401-0.371-2.401 0.846v0.515h3.064V4.63zM188.396 28.097c-0.595 0-1.2-0.083-1.813-0.251l0.586-2.143c1.009 0.287 2.299 0.304 2.831-0.598 0.621-1.068 0.399-1.444 0.432-18.247h2.231c-0.064 15.685 0.154 16.123-0.209 17.941C192.026 26.926 190.736 28.097 188.396 28.097M123.586 4.63h-2.232V2.403h2.232V4.63zM172.692 12.314c1.047-2.039 3.245-3.174 5.492-3.198V9.112h3.605l-1.274 0.635v10c-1.756-0.052-2.99 0.167-4.616-0.349 -2.22-0.698-3.724-2.406-3.724-4.885C172.175 13.716 172.347 12.982 172.692 12.314M172.692 20.206c2.905 2.07 6.866 1.728 7.073 1.767l0.75-1.571c-0.121 1.523 0.419 3.651-1.004 4.717 -1.203 0.899-3.348 0.87-4.672 0.556 -0.548-0.129-1.176-0.352-1.883-0.667l-0.697 2.143c1.414 0.521 2.766 0.946 4.267 0.946 2.176 0 3.756-0.515 4.742-1.544 0.985-1.031 1.478-2.418 1.478-4.162V6.858c-2.746 0.086-4.829-0.277-7.335 0.585C168.696 9.752 168.562 17.266 172.692 20.206M119.123 4.63h-2.232V2.403h2.232V4.63zM96.028 6.834h2.304v20.911h-1.886L81.607 8.936l1.312 3.048v15.761h-2.305V6.834h2.215l14.17 17.99 -0.971-2.566V6.834zM9.009 27.894L0 6.834h2.634c0.134 0.324 7.314 17.679 8.042 19.439l-0.228-2.298 7.121-17.141h2.543l-9.008 21.06H9.009zM48.932 6.834h2.364v20.91h-2.364V6.834zM74.29 17.349c0 4.75-3.411 8.573-8.2 8.573s-8.261-3.883-8.261-8.632v-0.061c0-4.749 3.413-8.572 8.201-8.572 4.789 0 8.26 3.883 8.26 8.633V17.349zM66.09 6.476c-6.375 0-10.715 5.048-10.715 10.814v0.059c0 5.765 4.28 10.754 10.655 10.754s10.715-5.048 10.715-10.813v-0.061C76.745 11.465 72.465 6.476 66.09 6.476M45.031 22.069v0.059c0 3.584-2.993 5.915-7.153 5.915 -3.322 0-6.045-1.105-8.559-3.346l1.466-1.732c2.185 1.971 4.28 2.956 7.184 2.956 2.813 0 4.668-1.492 4.668-3.554v-0.059c0-5.836-12.48-1.986-12.48-10.008v-0.059c0-3.286 2.903-5.706 6.884-5.706 3.052 0 5.237 0.867 7.362 2.57l-1.376 1.821c-4.75-3.864-10.506-2.384-10.506 1.106v0.06c0 1.971 1.077 3.076 5.687 4.062C42.877 17.17 45.031 18.873 45.031 22.069M25.688 27.745h-2.364V6.835h2.364V27.745zM163.293 30.096h2.232v2.227h-2.232V30.096zM158.83 30.096h2.232v2.227h-2.232V30.096zM18.71 81.609h19.398v9.649H0.461v-8.85c18.811-15.964 25.728-19.084 25.728-26.04 0-4.28-2.835-6.601-6.832-6.601 -3.925 0-6.614 2.175-10.393 6.817l-7.849-6.309C6.13 43.456 11 39.756 20.084 39.756c10.538 0 17.515 6.166 17.515 15.669v0.144c0 11.916-9.021 15.757-24.49 28.223l-2.16 1.664L18.71 81.609zM186.725 66.015c0 8.704-6.25 15.813-15.263 15.813 -9.012 0-15.407-7.254-15.407-15.959v-0.145c0-8.704 6.251-15.812 15.262-15.812 9.011 0 15.408 7.254 15.408 15.957V66.015zM171.462 39.612c-15.697 0-27.108 11.823-27.108 26.257v0.146c0 14.435 11.264 26.113 26.963 26.113s27.108-11.824 27.108-26.259v-0.145C198.425 51.29 187.16 39.612 171.462 39.612"/>
<path fill="#29367B" d="M68.518 36.272L71.095 38.74 68.622 41.311 66.046 38.843"/>
<path fill="#B1AEB3" d="M74.415 38.843L76.99 41.311 79.463 38.74 76.887 36.272"/>
<path fill="#D0DA33" d="M70.787 42.084L72.794 44.005 74.718 42.003 72.713 40.082"/>
<path fill="#71BA44" d="M78.142 43.183L80.148 45.105 82.072 43.103 80.066 41.181"/>
<path fill="#D0DA33" d="M67.507 45.145L69.513 47.067 71.437 45.065 69.433 43.143"/>
<path fill="#27B8BE" d="M74.238 45.378L76.243 47.3 78.168 45.298 76.162 43.376"/>
<path fill="#71BA44" d="M71.605 47.681L73.016 49.033 74.37 47.624 72.959 46.272"/>
<path fill="#58B75E" d="M76.928 48.742L78.339 50.094 79.694 48.685 78.283 47.334"/>
<path fill="#3088C8" d="M88.918 37.593H93.36800000000001V42.035000000000004H88.918z" transform="rotate(-10.39 91.135 39.818)"/>
<path fill="#1E3871" d="M83.133 39.508H86.70299999999999V43.072H83.133z" transform="rotate(-10.376 84.927 41.294)"/>
<path fill="#27B8BE" d="M89.92 44.395H93.49V47.959H89.92z" transform="rotate(-10.39 91.7 46.17)"/>
<path fill="#D0DA33" d="M85.014 44.984H87.794V47.758H85.014z" transform="rotate(-10.382 86.413 46.375)"/>
<path fill="#B1AEB3" d="M90.334 50.169H93.114V52.943999999999996H90.334z" transform="rotate(-10.382 91.733 51.56)"/>
<path fill="#27B8BE" d="M80.917 46.094H83.69800000000001V48.869H80.917z" transform="rotate(-10.382 82.315 47.485)"/>
<path fill="#71BA44" d="M85.88 49.669H88.66V52.443999999999996H85.88z" transform="rotate(-10.382 87.28 51.06)"/>
<path fill="#B1AEB3" d="M82.329 50.08H84.285V52.032H82.329z" transform="rotate(-10.39 83.314 51.066)"/>
<path fill="#58B75E" d="M86.024 54.05H87.979V56.001H86.024z" transform="rotate(-10.36 87.01 55.033)"/>
<path fill="#29367B" d="M101.572 52.342L99.659 56.354 103.679 58.262 105.59 54.251"/>
<path fill="#27B8BE" d="M95.867 50.489L94.333 53.707 97.557 55.238 99.091 52.02"/>
<path fill="#71BA44" d="M98.505 58.416L96.971 61.635 100.196 63.165 101.73 59.947"/>
<path fill="#D0DA33" d="M94.28 56.002L93.086 58.508 95.597 59.7 96.792 57.194"/>
<path fill="#D0DA33" d="M95.555 63.315L94.36 65.821 96.87 67.013 98.066 64.507"/>
<path fill="#3088C8" d="M90.306 54.51L89.111 57.016 91.622 58.208 92.817 55.702"/>
<path fill="#B1AEB3" d="M92.237 60.308L91.042 62.814 93.552 64.006 94.748 61.5"/>
<path fill="#71BA44" d="M89.217 58.541L88.376 60.304 90.142 61.142 90.983 59.38"/>
<path fill="#27B8BE" d="M89.372 64.241H91.324V66.196H89.372z" transform="rotate(-64.552 90.342 65.217)"/>
<path fill="#3088C8" d="M103.737 71.551L99.835 73.687 101.976 77.582 105.878 75.446"/>
<path fill="#27B8BE" d="M100.198 66.717L97.068 68.431 98.785 71.555 101.915 69.841"/>
<path fill="#27B8BE" d="M97.691 74.686L94.56 76.4 96.278 79.525 99.408 77.811"/>
<path fill="#29367B" d="M95.679 70.261L93.242 71.596 94.578 74.028 97.017 72.694"/>
<path fill="#58B75E" d="M92.426 76.936L89.988 78.271 91.325 80.703 93.763 79.369"/>
<path fill="#1E3871" d="M93.331 66.731L90.893 68.065 92.23 70.499 94.669 69.164"/>
<path fill="#D0DA33" d="M91.498 72.56L89.06 73.895 90.397 76.327 92.835 74.993"/>
<path fill="#B1AEB3" d="M90.084 69.364L88.369 70.303 89.31 72.014 91.024 71.076"/>
<path fill="#27B8BE" d="M87.475 74.114L85.76 75.053 86.7 76.764 88.415 75.826"/>
<path fill="#71BA44" d="M89.529 88.1H93.972V92.55099999999999H89.529z" transform="rotate(-82.958 91.74 90.32)"/>
<path fill="#1E3871" d="M90.424 82.17H93.988V85.74H90.424z" transform="rotate(-82.935 92.21 83.955)"/>
<path fill="#27B8BE" d="M83.719 87.169H87.28299999999999V90.74H83.719z" transform="rotate(-82.935 85.504 88.955)"/>
<path fill="#71BA44" d="M85.516 82.459H88.29100000000001V85.239H85.516z" transform="rotate(-82.952 86.912 83.852)"/>
<path fill="#B1AEB3" d="M78.966 85.97H81.741V88.75H78.966z" transform="rotate(-82.952 80.36 87.365)"/>
<path fill="#B1AEB3" d="M85.681 78.224H88.457V81.00500000000001H85.681z" transform="rotate(-82.935 87.072 79.615)"/>
<path fill="#71BA44" d="M80.778 81.88H83.55300000000001V84.661H80.778z" transform="rotate(-82.935 82.168 83.27)"/>
<path fill="#B1AEB3" d="M82.377 78.518H84.329V80.473H82.377z" transform="rotate(-82.946 83.36 79.498)"/>
<path fill="#B1AEB3" d="M77.476 80.846H79.428V82.802H77.476z" transform="rotate(-82.952 78.455 81.827)"/>
<path fill="#D0DA33" d="M76.652 96.502L73.39 93.479 70.362 96.735 73.624 99.757"/>
<path fill="#71BA44" d="M76.051 34.279L72.789 31.256 69.761 34.512 73.023 37.534"/>
<path fill="#27B8BE" d="M80.133 91.625L77.516 89.2 75.087 91.812 77.704 94.236"/>
<path fill="#71BA44" d="M71.765 91.764L69.147 89.339 66.718 91.951 69.335 94.375"/>
<path fill="#3088C8" d="M75.337 88.463L73.299 86.575 71.408 88.608 73.445 90.496"/>
<path fill="#D0DA33" d="M67.965 87.486L65.928 85.598 64.037 87.631 66.075 89.52"/>
<path fill="#D0DA33" d="M77.954 85.126L75.916 83.238 74.025 85.271 76.062 87.159"/>
<path fill="#B1AEB3" d="M71.833 85.227L69.795 83.339 67.904 85.372 69.941 87.26"/>
<path fill="#1E3871" d="M74.426 82.881L72.993 81.552 71.663 82.983 73.096 84.311"/>
<path fill="#B1AEB3" d="M69.087 81.907L67.654 80.579 66.323 82.009 67.756 83.337"/>
<path fill="#3088C8" d="M52.797 88.848H57.248V93.28999999999999H52.797z" transform="rotate(-11.333 55.022 91.072)"/>
<path fill="#27B8BE" d="M59.437 87.709H63.007V91.273H59.437z" transform="rotate(-11.333 61.207 89.493)"/>
<path fill="#71BA44" d="M52.569 82.934H56.139V86.49799999999999H52.569z" transform="rotate(-11.344 54.34 84.698)"/>
<path fill="#D0DA33" d="M58.262 83.048H61.042V85.82300000000001H58.262z" transform="rotate(-11.333 59.64 84.44)"/>
<path fill="#29367B" d="M62.34 81.869H65.12V84.645H62.34z" transform="rotate(-11.333 63.72 83.263)"/>
<path fill="#3088C8" d="M57.317 78.377H60.097V81.152H57.317z" transform="rotate(-11.333 58.696 79.77)"/>
<path fill="#71BA44" d="M61.693 78.724H63.649V80.676H61.693z" transform="rotate(-11.314 62.682 79.71)"/>
<path fill="#B1AEB3" d="M57.934 74.815H59.888999999999996V76.767H57.934z" transform="rotate(-11.344 58.916 75.795)"/>
<path fill="#27B8BE" d="M44.388 78.715L46.233 74.673 42.182 72.831 40.337 76.873"/>
<path fill="#D0DA33" d="M50.122 80.474L51.602 77.231 48.353 75.754 46.872 78.997"/>
<path fill="#3088C8" d="M47.351 72.592L48.832 69.349 45.582 67.871 44.102 71.115"/>
<path fill="#27B8BE" d="M49.54 71.708H52.316V74.487H49.54z" transform="rotate(-65.485 50.93 73.102)"/>
<path fill="#B1AEB3" d="M55.002 81.249L56.155 78.724 53.625 77.574 52.472 80.099"/>
<path fill="#29367B" d="M48.145 64.418H50.92100000000001V67.197H48.145z" transform="rotate(-65.474 49.535 65.806)"/>
<path fill="#58B75E" d="M53.538 73.134H56.31399999999999V75.913H53.538z" transform="rotate(-65.474 54.928 74.522)"/>
<path fill="#D0DA33" d="M51.512 67.369H54.288V70.148H51.512z" transform="rotate(-65.455 52.905 68.762)"/>
<path fill="#1E3871" d="M56.637 72.312L57.448 70.536 55.668 69.726 54.857 71.503"/>
<path fill="#71BA44" d="M55.879 66.948L56.69 65.171 54.91 64.362 54.099 66.139"/>
<path fill="#27B8BE" d="M41.902 59.544L45.769 57.344 43.563 53.485 39.697 55.685"/>
<path fill="#71BA44" d="M45.522 64.32L48.624 62.555 46.854 59.459 43.753 61.225"/>
<path fill="#1E3871" d="M47.897 56.31L50.999 54.545 49.229 51.448 46.128 53.214"/>
<path fill="#3088C8" d="M49.982 60.701L52.396 59.326 51.019 56.916 48.604 58.29"/>
<path fill="#71BA44" d="M53.124 53.974L55.538 52.599 54.161 50.189 51.746 51.563"/>
<path fill="#27B8BE" d="M52.387 64.192L54.803 62.818 53.426 60.407 51.01 61.782"/>
<path fill="#D0DA33" d="M54.124 58.334L56.539 56.96 55.162 54.549 52.747 55.924"/>
<path fill="#B1AEB3" d="M55.591 61.506L57.29 60.539 56.321 58.844 54.622 59.81"/>
<path fill="#71BA44" d="M58.12 56.713L59.82 55.746 58.851 54.051 57.151 55.018"/>
<path fill="#71BA44" d="M51.354 38.35H55.796V42.800000000000004H51.354z" transform="rotate(-83.898 53.578 40.576)"/>
<path fill="#B1AEB3" d="M51.445 45.166H55.008V48.736999999999995H51.445z" transform="rotate(-83.892 53.227 46.953)"/>
<path fill="#27B8BE" d="M58.066 40.057H61.63V43.628H58.066z" transform="rotate(-83.892 59.848 41.844)"/>
<path fill="#D0DA33" d="M57.142 45.581H59.917V48.361000000000004H57.142z" transform="rotate(-83.886 58.533 46.972)"/>
<path fill="#3088C8" d="M63.634 41.961H66.409V44.741H63.634z" transform="rotate(-83.91 65.016 43.35)"/>
<path fill="#27B8BE" d="M57.047 49.818H59.821999999999996V52.598H57.047z" transform="rotate(-83.886 58.435 51.21)"/>
<path fill="#71BA44" d="M61.889 46.08H64.664V48.86H61.889z" transform="rotate(-83.886 63.278 47.47)"/>
<path fill="#1E3871" d="M61.176 50.287H63.128V52.243H61.176z" transform="rotate(-83.892 62.153 51.266)"/>
<path fill="#B1AEB3" d="M66.039 47.877H67.991V49.834H66.039z" transform="rotate(-83.927 67.016 48.854)"/>
<path fill="#727A82" d="M86.941 68.235c-2.33-1.342-5.572-1.732-8.931-0.502 -1.468 0.555-2.938 1.524-4.178 2.473 -0.781-4.735-0.4-9.13-0.218-11.289 0.001-0.012 0.015-0.018 0.026-0.011 1.677 1.055 1.207 4.003 0.855 4.846 -0.007 0.017 0.018 0.028 0.03 0.014 2.09-2.468 1.069-5.193 0.64-5.883 -0.078-0.125 0.395 0.254 0.783 0.751 1.461 1.86 0.786 3.949 0.949 3.9 0.004-0.001 0.009 0 0.011-0.005 0.971-1.905 0.659-4.321-1.338-6.066 -0.091-0.077 0.265 0.039 0.378 0.079 1.703 0.591 2.591 2.345 2.579 3.909 0 0.344 1.15-2.938-1.63-4.606 -0.52-0.311-1.054-0.482-1.403-0.524 -0.272-0.029 1.489-0.294 2.726 0.544 0.743 0.501 0.938 1.107 0.919 0.937 -0.216-2.197-2.806-3.065-4.376-2.406 -0.166 0.083 0.618-1.079 2.134-0.989 0.292 0.017 0.562 0.08 0.778 0.183 0.015 0.007 0.029-0.013 0.018-0.026 -1.145-1.309-3.029-1.336-4.029 0.178 -0.007 0.01-0.022 0.01-0.028 0 -0.379-0.679-0.696-1.548-0.833-2.368 -0.033-0.21-0.103 1.015-0.863 2.368 -0.006 0.01-0.02 0.01-0.027 0 -1.003-1.518-2.885-1.488-4.029-0.178 -0.012 0.013 0.002 0.033 0.018 0.026 0.764-0.363 2.207-0.256 2.93 0.781 0.009 0.014-0.004 0.032-0.02 0.025 -1.618-0.68-4.16 0.243-4.375 2.406 -0.001 0.018 0.023 0.028 0.032 0.012 0.072-0.135 0.25-0.423 0.583-0.715 1.294-1.14 3.329-0.81 3.03-0.778 -0.795 0.098-2.427 0.806-3.03 2.424 -0.493 1.322-0.018 3.063 0 2.632 0.016-1.471 0.859-3.441 2.94-3.943 0.016-0.004 0.028 0.018 0.015 0.029 -3.234 2.843-1.278 6.334-1.308 6.058 -0.1-1.127-0.183-3.139 1.686-4.658 0.014-0.011 0.036 0.004 0.026 0.02 -0.429 0.69-1.449 3.415 0.64 5.883 0.012 0.014 0.037 0.003 0.03-0.014 -0.351-0.843-0.821-3.791 0.855-4.846 0.011-0.007 0.025-0.001 0.026 0.011 0.183 2.159 0.564 6.554-0.218 11.289 -2.656-2.03-5.271-3.361-8.639-3.213 -1.694 0.074-3.301 0.567-4.47 1.242 -0.042 0.024-0.011 0.085 0.034 0.071 4.899-1.566 9.139 0.334 13.081 3.614 -1.991 1.735-3.798 3.551-4.848 4.099 -0.245-0.335-0.67-0.528-1.135-0.427 -0.876 0.193-1.085 1.29-0.61 1.762 1.721 1.717 4.412-1.69 7.631-4.538 3.098 2.738 5.913 6.254 7.631 4.538 0.137-0.137 0.296-0.589 0.214-0.948 -0.207-0.896-1.398-1.155-1.959-0.387 -1.01-0.509-2.783-2.301-4.848-4.099 3.97-3.309 8.164-5.156 13.08-3.614C86.952 68.32 86.983 68.259 86.941 68.235"/>
<path fill="#ADA9AE" d="M71.265 115.108h1.277v-11.436h-1.277V115.108zM37.804 113.833l-1.995-4.891c2.064-1.747 4.625-1.426 6.273 0l-1.995 4.891H37.804zM30.764 113.833c-1.004-0.03-1.71 0.095-2.642-0.198 -3.451-1.09-2.513-5.888 1.349-5.888h1.293V113.833zM66.268 113.833c-2.057-0.063-3.91 0.363-4.853-1.274 0.186-0.818 0.084-1.552 0.112-5.306H60.25v4.318c0 3.169-3.824 2.963-4.726 1.06 -0.362-0.765-0.141-1.085-0.207-5.378h-1.278v4.414c0 1.44-0.901 2.166-2.155 2.166H49.41v-6.58h-1.277v6.58h-6.705l2.235-5.337c-1.268-1.108-3.033-2.151-4.725-2.151 -1.719 0-3.45 0.998-4.726 2.151l2.251 5.337h-4.422v-7.376h-2.043c-2.888 0-5.285 1.66-5.285 4.381 0 4.843 5.941 4.246 6.051 4.27 -0.02 0.157 0.159 1.251-0.575 1.801 -0.686 0.514-1.911 0.499-2.674 0.319 -0.314-0.075-0.673-0.202-1.077-0.383l-0.399 1.227c0.81 0.298 1.583 0.542 2.442 0.542 2.434 0 3.56-1.214 3.56-3.267v-0.239c20.842-0.131 20.226 0.3 21.496-0.398 0.484-0.265 0.875-0.621 1.173-1.068 0.309 0.51 0.729 0.909 1.261 1.196 1.117 0.599 2.821 0.543 3.856 0.031 0.537-0.265 0.95-0.674 1.237-1.227 0.493 0.842 1.258 1.383 2.794 1.451v0.015h3.687v-11.436h-1.277V113.833zM180.858 113.268c-1.13 2.641-5.02 2.717-6.146-0.04 -0.331-0.809-0.331-1.768 0-2.589 1.363-3.378 6.41-2.408 6.41 1.299C181.122 112.415 181.034 112.859 180.858 113.268M168.47 113.268c-0.977 2.27-3.921 2.606-5.46 1.02 -1.263-1.306-1.226-3.433-0.007-4.716 0.993-1.055 2.524-1.225 3.695-0.742C168.544 109.6 169.159 111.667 168.47 113.268M137.412 113.833c-0.977-0.03-1.685 0.098-2.611-0.206 -3.363-1.105-2.561-5.896 1.349-5.896h1.262V113.833zM186.389 113.833H182c1.37-3.007-0.758-6.532-4.263-6.532 -3.228 0-5.486 3.308-4.167 6.532h-3.959c1.335-2.919-0.688-6.532-4.262-6.532 -3.23 0-5.486 3.309-4.167 6.532h-4.055v-10.161h-1.277v10.161h-4.07c-0.07-1.309 0.297-3.246-0.799-4.469 -0.743-0.838-1.57-1.099-2.698-1.099h-5.651l2.404-4.593h-1.389l-2.452 4.577c0.079 0.508 0.233 0.905 0.575 1.29 6.387 0.054 6.804-0.134 7.559 0.184 0.592 0.245 0.953 0.692 1.078 1.235 0.152 0.654 0.075 1.146 0.096 2.875h-11.814v-7.376h-2.012c-3.384 0-5.268 2.013-5.268 4.333 0 2.907 2.338 4.318 5.268 4.318h25.335c1.879 2.024 5.042 1.852 6.769 0h5.619c1.873 2.018 5.033 1.859 6.77 0h6.497v-11.436h-1.278V113.833zM191.386 115.108h1.277v-11.436h-1.277V115.108zM97.288 117.914H98.6v-1.309h-1.312V117.914zM109.774 113.833l-1.996-4.891c2.048-1.734 4.605-1.441 6.274 0l-1.995 4.891H109.774zM118.825 113.833h-5.427l2.234-5.337c-1.271-1.114-3.042-2.151-4.725-2.151 -1.725 0-3.451 1.003-4.726 2.151l2.251 5.337h-4.756v-7.376h-1.278v8.922c0 1.127-0.082 1.96-1.213 1.96 -0.263 0-0.402-0.023-0.654-0.096l-0.335 1.228c1.067 0.292 2.327 0.194 2.953-0.726 0.566-0.833 0.499-1.983 0.527-2.637h16.427v-11.436h-1.278V113.833zM21.872 107.572c-1.42-1.55-3.064-0.988-3.624-1.115v1.274c0.932 0.036 1.363-0.103 1.995 0.199 0.532 0.256 0.908 0.669 1.062 1.251 0.173 0.669 0.076 1.191 0.104 4.652h-5.987v1.275h7.264c-0.053-4.83 0.132-5.342-0.2-6.421C22.353 108.252 22.148 107.88 21.872 107.572M92.619 117.929h1.312v-1.309h-1.312V117.929zM84.854 113.833c-0.975-0.03-1.684 0.098-2.611-0.206 -3.367-1.106-2.556-5.896 1.349-5.896h1.262V113.833zM98.583 106.457h-1.277v7.376h-4.821v-7.376h-1.277v7.376h-5.077v-7.376c-1.606 0.05-4.002-0.325-5.843 1.234 -2.61 2.211-1.937 7.417 3.832 7.417h14.463V106.457zM90.382 117.929h1.312v-1.309h-1.312V117.929zM5.037 103.672H3.725v1.309h1.312V103.672zM136.446 103.672h-1.312v1.309h1.312V103.672zM86.125 103.672h-1.312v1.309h1.312V103.672zM12.571 117.929h1.312v-1.309h-1.312V117.929zM83.888 103.672h-1.312v1.309h1.312V103.672zM7.274 103.672H5.962v1.309h1.312V103.672zM10.334 117.929h1.312v-1.309h-1.312V117.929zM6.003 113.833c-0.973-0.03-1.686 0.098-2.61-0.206 -3.361-1.102-2.567-5.896 1.349-5.896h1.261V113.833zM13.634 106.457h-1.277v7.376H7.28v-7.376c-1.531 0.047-2.748-0.153-4.183 0.326 -1.203 0.404-2.186 1.147-2.722 2.287 -0.558 1.186-0.457 2.696 0.057 3.664 0.925 1.746 2.86 2.374 4.836 2.374h8.366V106.457zM123.822 115.108h1.277v-11.436h-1.277V115.108zM138.682 103.672h-1.312v1.309h1.312V103.672z"/>
<path fill="#ADA9AE" d="M1.251 123.484L2.854 123.484 2.854 128.062 7.193 123.484 9.161 123.484 5.369 127.399 9.33 132.588 7.389 132.588 4.275 128.491 2.854 129.948 2.854 132.588 1.251 132.588"/>
<path fill="#ADA9AE" d="M10.792 123.484H12.395V132.588H10.792z"/>
<path fill="#ADA9AE" d="M14.863 123.484L16.349 123.484 21.236 129.779 21.236 123.484 22.812 123.484 22.812 132.588 21.47 132.588 16.44 126.111 16.44 132.588 14.863 132.588"/>
<path fill="#ADA9AE" d="M24.797 128.062v-0.026c0-2.549 1.955-4.709 4.704-4.709 1.59 0 2.568 0.443 3.506 1.236l-1.016 1.21c-2.47-2.098-5.512-0.587-5.512 2.237v0.026c0 1.873 1.237 3.251 3.1 3.251 0.861 0 1.643-0.273 2.203-0.689v-1.704h-2.333v-1.391h3.884v3.823C30.071 134.113 24.797 132.681 24.797 128.062"/>
<path fill="#ADA9AE" d="M38.948 131.131c4.254 0 4.203-6.191 0-6.191H37.15v6.191H38.948zM35.547 123.484h3.401c6.508 0 6.42 9.104 0 9.104h-3.401V123.484z"/>
<path fill="#ADA9AE" d="M53.155 128.062v-0.026c0-1.769-1.29-3.238-3.102-3.238 -1.811 0-3.075 1.443-3.075 3.212v0.026c0 1.769 1.29 3.239 3.101 3.239C51.891 131.275 53.155 129.831 53.155 128.062M45.297 128.062v-0.026c0-2.562 1.981-4.709 4.782-4.709 2.802 0 4.757 2.121 4.757 4.683v0.026c0 2.562-1.98 4.708-4.783 4.708C47.252 132.744 45.297 130.624 45.297 128.062"/>
<path fill="#ADA9AE" d="M56.827 123.484L58.534 123.484 61.31 127.789 64.085 123.484 65.792 123.484 65.792 132.588 64.189 132.588 64.189 126.059 61.31 130.351 61.258 130.351 58.404 126.085 58.404 132.588 56.827 132.588"/>
<path fill="#ADA9AE" d="M79.506 128.062v-0.026c0-1.769-1.29-3.238-3.102-3.238 -1.811 0-3.075 1.443-3.075 3.212v0.026c0 1.769 1.29 3.239 3.101 3.239C78.242 131.275 79.506 129.831 79.506 128.062M71.648 128.062v-0.026c0-2.562 1.981-4.709 4.782-4.709 2.802 0 4.757 2.121 4.757 4.683v0.026c0 2.562-1.981 4.708-4.783 4.708C73.603 132.744 71.648 130.624 71.648 128.062"/>
<path fill="#ADA9AE" d="M83.179 123.484L89.968 123.484 89.968 124.941 84.782 124.941 84.782 127.425 89.382 127.425 89.382 128.881 84.782 128.881 84.782 132.588 83.179 132.588"/>
<path fill="#ADA9AE" d="M95.027 131.262l0.964-1.145c0.873 0.755 1.746 1.184 2.88 1.184 0.99 0 1.616-0.455 1.616-1.145v-0.025c0-2.034-5.096-0.646-5.096-4.111v-0.026c0-2.794 3.911-3.507 6.425-1.495l-0.86 1.209c-2.098-1.561-3.961-0.947-3.961 0.144v0.026c0 2.005 5.095 0.737 5.095 4.096V130C102.09 133.192 97.564 133.521 95.027 131.262"/>
<path fill="#ADA9AE" d="M109.296 128.973l-1.577-3.642 -1.564 3.642H109.296zM107.002 123.419h1.485l4.014 9.169h-1.694l-0.925-2.198h-4.314l-0.937 2.198h-1.643L107.002 123.419z"/>
<path fill="#ADA9AE" d="M113.444 128.726v-5.242h1.603v5.177c0 1.691 0.873 2.601 2.306 2.601 1.421 0 2.294-0.858 2.294-2.536v-5.242h1.603v5.164c0 2.718-1.538 4.084-3.923 4.084C114.955 132.732 113.444 131.366 113.444 128.726"/>
<path fill="#ADA9AE" d="M126.997 131.131c4.254 0 4.204-6.191 0-6.191h-1.798v6.191H126.997zM123.596 123.484h3.401c6.508 0 6.42 9.104 0 9.104h-3.401V123.484z"/>
<path fill="#ADA9AE" d="M133.875 123.484H135.478V132.588H133.875z"/>
<path fill="#ADA9AE" d="M147.352 128.973l-1.577-3.642 -1.564 3.642H147.352zM145.058 123.419h1.485l4.014 9.169h-1.694l-0.925-2.198h-4.314l-0.937 2.198h-1.643L145.058 123.419z"/>
<path fill="#ADA9AE" d="M156.02 127.997c1.147 0 1.877-0.598 1.877-1.522v-0.026c0-0.975-0.704-1.509-1.89-1.509h-2.332v3.057H156.02zM152.072 123.484h4.065c1.928 0 3.389 0.969 3.389 2.874 -0.037 0.1 0.19 2.108-2.177 2.784l2.463 3.446h-1.89l-2.241-3.174c-0.175 0-2.109 0-2.006 0v3.174h-1.603V123.484z"/>
<path fill="#ADA9AE" d="M167.299 128.973l-1.577-3.642 -1.564 3.642H167.299zM165.005 123.419h1.486l4.013 9.169h-1.694l-0.925-2.198h-4.314l-0.937 2.198h-1.643L165.005 123.419z"/>
<path fill="#ADA9AE" d="M176.253 131.171c1.095 0 1.759-0.43 1.759-1.249v-0.026c0-0.767-0.612-1.223-1.876-1.223h-2.541v2.498H176.253zM175.784 127.321c1.029 0 1.72-0.403 1.72-1.236v-0.026c0-0.715-0.573-1.157-1.603-1.157h-2.306v2.419H175.784zM172.018 123.484h4.092c1.604 0 2.997 0.75 2.997 2.315 -0.037 0.1 0.164 1.294-1.303 2.055 1.069 0.364 1.811 0.976 1.811 2.211v0.026c0 1.626-1.342 2.497-3.375 2.497h-4.222V123.484z"/>
<path fill="#ADA9AE" d="M181.6 123.484H183.203V132.588H181.6z"/>
<path fill="#ADA9AE" d="M191.041 128.973l-1.577-3.642 -1.564 3.642H191.041zM188.747 123.419h1.485l4.014 9.169h-1.694l-0.925-2.198h-4.314l-0.937 2.198h-1.643L188.747 123.419z"/>
</g>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

8
static/image/vision2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

949
static/js/messages.js Normal file
View File

@ -0,0 +1,949 @@
/**
* Message System JavaScript
* Handles interactive features for the modern message interface
*/
class MessageSystem {
constructor() {
this.init();
}
init() {
this.initSearch();
this.initFolderNavigation();
this.initMessageActions();
this.initComposeFeatures();
this.initKeyboardShortcuts();
this.initAutoSave();
this.initAttachments();
this.initTooltips();
}
/**
* Initialize search functionality
*/
initSearch() {
const searchInputs = document.querySelectorAll('.search-input');
searchInputs.forEach(input => {
let searchTimeout;
input.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
const searchTerm = e.target.value.toLowerCase();
searchTimeout = setTimeout(() => {
this.performSearch(searchTerm);
}, 300);
});
// Clear search on Escape key
input.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.target.value = '';
this.performSearch('');
}
});
});
}
/**
* Perform search across message items
*/
performSearch(searchTerm) {
const messageItems = document.querySelectorAll('.message-item');
let visibleCount = 0;
messageItems.forEach(item => {
const text = item.textContent.toLowerCase();
if (text.includes(searchTerm)) {
item.style.display = 'flex';
visibleCount++;
} else {
item.style.display = 'none';
}
});
// Show no results message if needed
this.updateSearchResults(visibleCount, searchTerm);
}
/**
* Update search results display
*/
updateSearchResults(count, searchTerm) {
let noResults = document.querySelector('.search-no-results');
if (count === 0 && searchTerm) {
if (!noResults) {
noResults = document.createElement('div');
noResults.className = 'search-no-results';
noResults.innerHTML = `
<div class="text-center py-8">
<i class="fas fa-search text-4xl text-gray-400 mb-4"></i>
<h3 class="text-lg font-semibold text-gray-700 mb-2">
{% trans "No messages found" %}
</h3>
<p class="text-gray-500">
{% trans "No messages match your search for" %} "${searchTerm}"
</p>
</div>
`;
const messagesList = document.querySelector('.messages-list');
if (messagesList) {
messagesList.appendChild(noResults);
}
}
} else if (noResults) {
noResults.remove();
}
}
/**
* Initialize folder navigation
*/
initFolderNavigation() {
const folderItems = document.querySelectorAll('.folder-item');
folderItems.forEach(item => {
item.addEventListener('click', (e) => {
// Remove active class from all items
folderItems.forEach(f => f.classList.remove('active'));
// Add active class to clicked item
item.classList.add('active');
// Add loading state
this.showLoadingState();
// Navigate to folder (if it's a link)
if (item.tagName === 'A') {
// Let the link handle navigation
return;
}
});
});
}
/**
* Initialize message actions
*/
initMessageActions() {
// Refresh button
const refreshBtn = document.querySelector('.action-btn[title="Refresh"]');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
this.refreshMessages();
});
}
// Mark as read functionality
const markReadBtns = document.querySelectorAll('[onclick*="markAsRead"]');
markReadBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.markAsRead(btn);
});
});
// Delete message functionality
const deleteBtns = document.querySelectorAll('[onclick*="confirm"]');
deleteBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
if (!this.confirmDelete()) {
e.preventDefault();
}
});
});
}
/**
* Initialize compose features
*/
initComposeFeatures() {
const form = document.getElementById('composeForm');
if (!form) return;
// Auto-resize textarea
const textarea = form.querySelector('textarea[name="content"]');
if (textarea) {
textarea.addEventListener('input', () => {
this.autoResizeTextarea(textarea);
});
}
// Rich text toolbar
const toolbarBtns = document.querySelectorAll('.toolbar-btn');
toolbarBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.handleToolbarAction(btn);
});
});
// Save draft button
const saveDraftBtn = document.getElementById('saveDraftBtn');
if (saveDraftBtn) {
saveDraftBtn.addEventListener('click', () => {
this.saveDraft();
});
}
// Form submission
form.addEventListener('submit', (e) => {
this.handleFormSubmit(e);
});
}
/**
* Initialize keyboard shortcuts
*/
initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K for search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchInput = document.querySelector('.search-input');
if (searchInput) {
searchInput.focus();
}
}
// Ctrl/Cmd + N for new message
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
const composeBtn = document.querySelector('.compose-btn');
if (composeBtn) {
window.location.href = composeBtn.href;
}
}
// Escape to close modals
if (e.key === 'Escape') {
this.closeModals();
}
});
}
/**
* Initialize auto-save functionality
*/
initAutoSave() {
const form = document.getElementById('composeForm');
if (!form) return;
let autoSaveTimer;
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('input', () => {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
this.autoSave();
}, 30000); // Auto-save after 30 seconds
});
});
}
/**
* Initialize attachment handling
*/
initAttachments() {
const attachBtn = document.querySelector('.toolbar-btn[title*="Attach"]');
if (attachBtn) {
attachBtn.addEventListener('click', () => {
this.showAttachmentDialog();
});
}
}
/**
* Initialize tooltips
*/
initTooltips() {
const tooltipElements = document.querySelectorAll('[title]');
tooltipElements.forEach(element => {
element.addEventListener('mouseenter', (e) => {
this.showTooltip(e.target);
});
element.addEventListener('mouseleave', (e) => {
this.hideTooltip(e.target);
});
});
}
/**
* Refresh messages with animation
*/
refreshMessages() {
const refreshBtn = document.querySelector('.action-btn[title="Refresh"]');
if (refreshBtn) {
const icon = refreshBtn.querySelector('i');
icon.classList.add('fa-spin');
setTimeout(() => {
icon.classList.remove('fa-spin');
location.reload();
}, 1000);
}
}
/**
* Mark message as read
*/
async markAsRead(button) {
const messageId = button.getAttribute('data-message-id');
if (!messageId) return;
try {
const response = await fetch(`/messages/${messageId}/mark-read/`, {
method: 'POST',
headers: {
'X-CSRFToken': this.getCSRFToken(),
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (data.success) {
this.showNotification('{% trans "Message marked as read" %}', 'success');
location.reload();
} else {
this.showNotification('{% trans "Failed to mark message as read" %}', 'error');
}
} catch (error) {
console.error('Error marking message as read:', error);
this.showNotification('{% trans "An error occurred" %}', 'error');
}
}
/**
* Confirm delete action
*/
confirmDelete() {
return confirm('{% trans "Are you sure you want to delete this message?" %}');
}
/**
* Auto-resize textarea
*/
autoResizeTextarea(textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
/**
* Handle toolbar actions
*/
handleToolbarAction(button) {
const action = button.getAttribute('title');
const textarea = document.querySelector('textarea[name="content"]');
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
switch (action) {
case '{% trans "Bold" %}':
this.wrapText(textarea, '**', '**');
break;
case '{% trans "Italic" %}':
this.wrapText(textarea, '*', '*');
break;
case '{% trans "Underline" %}':
this.wrapText(textarea, '__', '__');
break;
case '{% trans "Bullet List" %}':
this.insertList(textarea, '- ');
break;
case '{% trans "Numbered List" %}':
this.insertList(textarea, '1. ');
break;
case '{% trans "Insert Link" %}':
this.insertLink(textarea);
break;
case '{% trans "Insert Image" %}':
this.insertImage(textarea);
break;
case '{% trans "Attach File" %}':
this.showAttachmentDialog();
break;
}
}
/**
* Wrap selected text with formatting
*/
wrapText(textarea, before, after) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
const replacement = before + selectedText + after;
textarea.value = textarea.value.substring(0, start) + replacement + textarea.value.substring(end);
textarea.selectionStart = start + before.length;
textarea.selectionEnd = start + before.length + selectedText.length;
textarea.focus();
}
/**
* Insert list
*/
insertList(textarea, marker) {
const start = textarea.selectionStart;
const text = marker + '\n';
textarea.value = textarea.value.substring(0, start) + text + textarea.value.substring(start);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
/**
* Insert link
*/
insertLink(textarea) {
const url = prompt('{% trans "Enter URL:" %}', 'https://');
if (url) {
const link = `[${url}](${url})`;
this.insertAtCursor(textarea, link);
}
}
/**
* Insert image
*/
insertImage(textarea) {
const url = prompt('{% trans "Enter image URL:" %}', 'https://');
if (url) {
const image = `![Image](${url})`;
this.insertAtCursor(textarea, image);
}
}
/**
* Insert text at cursor position
*/
insertAtCursor(textarea, text) {
const start = textarea.selectionStart;
textarea.value = textarea.value.substring(0, start) + text + textarea.value.substring(start);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
/**
* Save draft
*/
async saveDraft() {
const form = document.getElementById('composeForm');
if (!form) return;
const formData = new FormData(form);
try {
const response = await fetch('/messages/save-draft/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': this.getCSRFToken(),
},
});
const data = await response.json();
if (data.success) {
this.showNotification('{% trans "Draft saved" %}', 'success');
} else {
this.showNotification('{% trans "Failed to save draft" %}', 'error');
}
} catch (error) {
console.error('Error saving draft:', error);
this.showNotification('{% trans "An error occurred" %}', 'error');
}
}
/**
* Auto-save draft
*/
async autoSave() {
const form = document.getElementById('composeForm');
if (!form) return;
// Only auto-save if form has content
const hasContent = form.querySelector('textarea[name="content"]').value.trim() ||
form.querySelector('input[name="subject"]').value.trim();
if (!hasContent) return;
try {
const formData = new FormData(form);
await fetch('/messages/auto-save-draft/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': this.getCSRFToken(),
},
});
console.log('Draft auto-saved');
} catch (error) {
console.error('Error auto-saving draft:', error);
}
}
/**
* Handle form submission
*/
handleFormSubmit(e) {
const form = e.target;
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>{% trans "Sending..." %}';
}
}
/**
* Show attachment dialog
*/
showAttachmentDialog() {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = 'image/*,.pdf,.doc,.docx,.txt';
input.addEventListener('change', (e) => {
this.handleFileSelect(e.target.files);
});
input.click();
}
/**
* Handle file selection
*/
handleFileSelect(files) {
const attachmentsSection = document.getElementById('attachmentsSection');
const attachmentList = document.getElementById('attachmentList');
if (!attachmentsSection || !attachmentList) return;
attachmentsSection.style.display = 'block';
Array.from(files).forEach(file => {
const attachmentItem = this.createAttachmentItem(file);
attachmentList.appendChild(attachmentItem);
});
}
/**
* Create attachment item
*/
createAttachmentItem(file) {
const item = document.createElement('div');
item.className = 'attachment-item';
const icon = this.getFileIcon(file.type);
const size = this.formatFileSize(file.size);
item.innerHTML = `
<i class="fas ${icon} attachment-icon"></i>
<span class="attachment-name">${file.name}</span>
<span class="attachment-size">${size}</span>
<i class="fas fa-times attachment-remove" onclick="this.parentElement.remove()"></i>
`;
return item;
}
/**
* Get file icon based on MIME type
*/
getFileIcon(mimeType) {
if (mimeType.startsWith('image/')) return 'fa-image';
if (mimeType.includes('pdf')) return 'fa-file-pdf';
if (mimeType.includes('word')) return 'fa-file-word';
if (mimeType.includes('text')) return 'fa-file-alt';
return 'fa-file';
}
/**
* Format file size
*/
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Show loading state
*/
showLoadingState() {
const messagesList = document.querySelector('.messages-list');
if (messagesList) {
messagesList.style.opacity = '0.5';
}
}
/**
* Show notification
*/
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="fas ${this.getNotificationIcon(type)} me-2"></i>
${message}
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 100);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
/**
* Get notification icon
*/
getNotificationIcon(type) {
const icons = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
warning: 'fa-exclamation-triangle',
info: 'fa-info-circle'
};
return icons[type] || icons.info;
}
/**
* Show tooltip
*/
showTooltip(element) {
const title = element.getAttribute('title');
if (!title) return;
const tooltip = document.createElement('div');
tooltip.className = 'tooltip';
tooltip.textContent = title;
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
tooltip.style.top = rect.top - tooltip.offsetHeight - 5 + 'px';
element.setAttribute('data-original-title', title);
element.removeAttribute('title');
}
/**
* Hide tooltip
*/
hideTooltip(element) {
const tooltip = document.querySelector('.tooltip');
if (tooltip) {
tooltip.remove();
}
const originalTitle = element.getAttribute('data-original-title');
if (originalTitle) {
element.setAttribute('title', originalTitle);
element.removeAttribute('data-original-title');
}
}
/**
* Close modals
*/
closeModals() {
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.style.display = 'none';
});
}
/**
* Get CSRF token
*/
getCSRFToken() {
const cookie = document.cookie.split(';').find(c => c.trim().startsWith('csrftoken='));
return cookie ? cookie.split('=')[1] : '';
}
/**
* Initialize reply functionality
*/
initReplyFunctionality() {
// Handle reply button clicks
const replyBtns = document.querySelectorAll('.reply-btn');
replyBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.handleReplyClick(btn);
});
});
// Handle reply form submissions
const replyForms = document.querySelectorAll('#reply-form');
replyForms.forEach(form => {
form.addEventListener('submit', (e) => {
e.preventDefault();
this.handleReplySubmit(form);
});
});
// Handle cancel button in reply forms
const cancelBtns = document.querySelectorAll('[onclick*="hideReplyForm"]');
cancelBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.hideReplyForm(btn.closest('#reply-section'));
});
});
}
/**
* Handle reply button click
*/
async handleReplyClick(button) {
const messageId = button.getAttribute('data-message-id');
if (!messageId) return;
try {
// Show loading state
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> Loading...';
button.disabled = true;
// Fetch reply form via AJAX
const response = await fetch(`/messages/${messageId}/reply/`, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': this.getCSRFToken(),
},
});
const data = await response.json();
if (data.success) {
// Show reply form
const replySection = document.getElementById('reply-section');
if (replySection) {
replySection.innerHTML = data.html;
// Focus on the textarea
const textarea = replySection.querySelector('textarea[name="content"]');
if (textarea) {
setTimeout(() => textarea.focus(), 100);
}
}
} else {
this.showNotification('Failed to load reply form', 'error');
}
// Restore button state
button.innerHTML = originalText;
button.disabled = false;
} catch (error) {
console.error('Error loading reply form:', error);
this.showNotification('An error occurred while loading reply form', 'error');
// Restore button state
button.innerHTML = originalText;
button.disabled = false;
}
}
/**
* Handle reply form submission
*/
async handleReplySubmit(form) {
const submitBtn = form.querySelector('button[type="submit"]');
const textarea = form.querySelector('textarea[name="content"]');
const content = textarea.value.trim();
if (!content) {
this.showNotification('Reply content cannot be empty', 'error');
textarea.focus();
return;
}
try {
// Show loading state
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> Sending...';
submitBtn.disabled = true;
const formData = new FormData(form);
const response = await fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': this.getCSRFToken(),
},
});
const data = await response.json();
if (data.success) {
this.showNotification(data.message, 'success');
// Add the reply to the conversation (if on detail page)
this.addReplyToConversation(data);
// Hide the reply form
this.hideReplyForm(form.closest('#reply-section'));
} else {
this.showNotification(data.error || 'Failed to send reply', 'error');
}
// Restore button state
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
} catch (error) {
console.error('Error sending reply:', error);
this.showNotification('An error occurred while sending reply', 'error');
// Restore button state
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
}
/**
* Add reply to conversation
*/
addReplyToConversation(replyData) {
const conversationContainer = document.querySelector('.conversation-container');
if (!conversationContainer) return;
// Create new reply element
const replyElement = document.createElement('div');
replyElement.className = 'message-reply fade-in';
replyElement.innerHTML = `
<div class="flex items-start space-x-3 mb-4">
<div class="flex-shrink-0">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center">
<span class="text-white text-sm font-medium">${replyData.sender_name || 'You'}</span>
</div>
</div>
<div class="flex-grow">
<div class="bg-gray-50 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-900">Reply</h4>
<span class="text-xs text-gray-500">${replyData.reply_time}</span>
</div>
<p class="text-sm text-gray-700 whitespace-pre-wrap">${replyData.reply_content}</p>
</div>
</div>
</div>
`;
conversationContainer.appendChild(replyElement);
// Scroll to the new reply
replyElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
/**
* Hide reply form
*/
hideReplyForm(replySection) {
if (replySection) {
replySection.innerHTML = '';
}
}
}
// Initialize the message system when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.messageSystem = new MessageSystem();
// Initialize reply functionality
if (window.messageSystem) {
window.messageSystem.initReplyFunctionality();
}
});
// Add notification styles
const notificationStyles = `
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 9999;
transform: translateX(100%);
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.notification.show {
transform: translateX(0);
}
.notification-success {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
}
.notification-error {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
.notification-warning {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
}
.notification-info {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
}
.tooltip {
position: absolute;
background: #1f2937;
color: white;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
white-space: nowrap;
z-index: 9999;
pointer-events: none;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: #1f2937;
}
`;
// Add styles to head
const styleSheet = document.createElement('style');
styleSheet.textContent = notificationStyles;
document.head.appendChild(styleSheet);

View File

@ -179,8 +179,9 @@
{% if request.user.is_superuser %}
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'admin_settings' %}"><i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'easy_logs' %}"><i class="fas fa-history me-3 fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'admin_settings' %}"><i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'source_list' %}"><i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Integration" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'easy_logs' %}"><i class="fas fa-history me-3 fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
{% comment %} <li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li> {% endcomment %}
{% endif %}
{% endif %}

View File

@ -1,16 +1,16 @@
{% extends "base.html" %}
{% load static %}
{% load static i18n %}
{% block title %}Sources{% endblock %}
{% block title %}{% trans "Sources" %}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">Sources</h1>
<h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1>
<a href="{% url 'source_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i> Create Source
{% trans "Create Source for Integration" %} <i class="fas fa-plus"></i>
</a>
</div>
@ -29,11 +29,11 @@
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-outline-primary">
<i class="fas fa-search"></i> Search
<i class="fas fa-search"></i> {% trans "Search" %}
</button>
{% if search_query %}
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times"></i> Clear
<i class="fas fa-times"></i> {% trans "Clear" %}
</a>
{% endif %}
</div>
@ -56,12 +56,12 @@
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Name</th>
<th>Type</th>
<th>Status</th>
<th>API Key</th>
<th>Created</th>
<th>Actions</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "API Key" %}</th>
<th>{% trans "Created" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@ -71,16 +71,16 @@
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none">
<strong>{{ source.name }}</strong>
</a>
</td>
<td>
<span class="badge bg-info">{{ source.source_type }}</span>
</td>
<td>
{% if source.is_active %}
<span class="badge bg-success">Active</span>
<span class="badge bg-success">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-secondary">Inactive</span>
<span class="badge bg-secondary">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td>
@ -165,16 +165,16 @@
{% else %}
<div class="text-center py-5">
<i class="fas fa-database fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No sources found</h5>
<h5 class="text-muted">{% trans "No sources found" %}</h5>
<p class="text-muted">
{% if search_query %}
No sources match your search criteria.
{% blocktrans with query=query %}No sources match your search criteria "{{ query }}".{% endblocktrans %}
{% else %}
Get started by creating your first source.
{% trans "Get started by creating your first source." %}
{% endif %}
</p>
<a href="{% url 'source_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i> Create Source
<i class="fas fa-plus"></i> {% trans "Create Source" %}
</a>
</div>
{% endif %}