add delete
This commit is contained in:
parent
1fad9839af
commit
a96e618a6a
@ -295,10 +295,10 @@ class JobPosting(Base):
|
|||||||
next_num = 1
|
next_num = 1
|
||||||
|
|
||||||
self.internal_job_id = f"{prefix}-{year}-{next_num:06d}"
|
self.internal_job_id = f"{prefix}-{year}-{next_num:06d}"
|
||||||
|
|
||||||
if self.department:
|
if self.department:
|
||||||
self.department = self.department.title()
|
self.department = self.department.title()
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def get_location_display(self):
|
def get_location_display(self):
|
||||||
|
|||||||
335
requirements.txt
335
requirements.txt
@ -1,146 +1,191 @@
|
|||||||
annotated-types
|
amqp==5.3.1
|
||||||
appdirs
|
annotated-types==0.7.0
|
||||||
asgiref
|
anthropic==0.63.0
|
||||||
asteval
|
anyio==4.11.0
|
||||||
astunparse
|
appdirs==1.4.4
|
||||||
attrs
|
arrow==1.3.0
|
||||||
blinker
|
asgiref==3.9.2
|
||||||
blis
|
asteval==1.0.6
|
||||||
boto3
|
astunparse==1.6.3
|
||||||
botocore
|
attrs==25.3.0
|
||||||
bw-migrations
|
billiard==4.2.2
|
||||||
bw2parameters
|
bleach==6.2.0
|
||||||
bw_processing
|
blessed==1.22.0
|
||||||
cached-property
|
blinker==1.9.0
|
||||||
catalogue
|
blis==1.3.0
|
||||||
certifi
|
boto3==1.40.37
|
||||||
channels
|
botocore==1.40.37
|
||||||
chardet
|
bw-migrations==0.2
|
||||||
charset-normalizer
|
bw2data==4.5
|
||||||
click
|
bw2parameters==1.1.0
|
||||||
cloudpathlib
|
bw_processing==1.0
|
||||||
confection
|
cached-property==2.0.1
|
||||||
constructive_geometries
|
catalogue==2.0.10
|
||||||
country_converter
|
celery==5.5.3
|
||||||
cymem
|
certifi==2025.8.3
|
||||||
dataflows-tabulator
|
channels==4.3.1
|
||||||
datapackage
|
chardet==5.2.0
|
||||||
deepdiff
|
charset-normalizer==3.4.3
|
||||||
Deprecated
|
click==8.3.0
|
||||||
Django
|
click-didyoumean==0.3.1
|
||||||
django-allauth
|
click-plugins==1.1.1.2
|
||||||
django-cors-headers
|
click-repl==0.3.0
|
||||||
django-filter
|
cloudpathlib==0.22.0
|
||||||
django-unfold
|
confection==0.1.5
|
||||||
djangorestframework
|
constructive_geometries==1.0
|
||||||
docopt
|
country_converter==1.3.1
|
||||||
|
crispy-bootstrap5==2025.6
|
||||||
|
cymem==2.0.11
|
||||||
|
dataflows-tabulator==1.54.3
|
||||||
|
datapackage==1.15.4
|
||||||
|
datastar-py==0.6.5
|
||||||
|
deepdiff==7.0.1
|
||||||
|
Deprecated==1.2.18
|
||||||
|
distro==1.9.0
|
||||||
|
Django==5.2.6
|
||||||
|
django-allauth==65.11.2
|
||||||
|
django-ckeditor-5==0.2.18
|
||||||
|
django-cors-headers==4.9.0
|
||||||
|
django-countries==7.6.1
|
||||||
|
django-crispy-forms==2.4
|
||||||
|
django-easy-audit==1.3.7
|
||||||
|
django-extensions==4.1
|
||||||
|
django-filter==25.1
|
||||||
|
django-picklefield==3.3
|
||||||
|
django-q2==1.8.0
|
||||||
|
django-summernote==0.8.20.0
|
||||||
|
django-template-partials==25.2
|
||||||
|
django-unfold==0.66.0
|
||||||
|
django-widget-tweaks==1.5.0
|
||||||
|
django_celery_results==2.6.0
|
||||||
|
djangorestframework==3.16.1
|
||||||
|
docopt==0.6.2
|
||||||
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
|
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
|
||||||
et_xmlfile
|
et_xmlfile==2.0.0
|
||||||
Faker
|
Faker==37.8.0
|
||||||
flexcache
|
flexcache==0.3
|
||||||
flexparser
|
flexparser==0.4
|
||||||
fsspec
|
fsspec==2025.9.0
|
||||||
idna
|
gpt-po-translator==1.3.2
|
||||||
ijson
|
greenlet==3.2.4
|
||||||
isodate
|
h11==0.16.0
|
||||||
Jinja2
|
httpcore==1.0.9
|
||||||
jmespath
|
httpx==0.28.1
|
||||||
jsonlines
|
idna==3.10
|
||||||
jsonpointer
|
ijson==3.4.0
|
||||||
jsonschema
|
iniconfig==2.1.0
|
||||||
jsonschema-specifications
|
isodate==0.7.2
|
||||||
langcodes
|
isort==5.13.2
|
||||||
language_data
|
Jinja2==3.1.6
|
||||||
linear-tsv
|
jiter==0.11.1
|
||||||
llvmlite
|
jmespath==1.0.1
|
||||||
loguru
|
jsonlines==4.0.0
|
||||||
lxml
|
jsonpointer==3.0.0
|
||||||
marisa-trie
|
jsonschema==4.25.1
|
||||||
markdown-it-py
|
jsonschema-specifications==2025.9.1
|
||||||
MarkupSafe
|
kombu==5.5.4
|
||||||
matrix_utils
|
langcodes==3.5.0
|
||||||
mdurl
|
language_data==1.3.0
|
||||||
morefs
|
linear-tsv==1.1.0
|
||||||
mrio-common-metadata
|
llvmlite==0.45.0
|
||||||
murmurhash
|
loguru==0.7.3
|
||||||
numba
|
lxml==6.0.2
|
||||||
numpy
|
marisa-trie==1.3.1
|
||||||
openpyxl
|
markdown-it-py==4.0.0
|
||||||
ordered-set
|
MarkupSafe==3.0.2
|
||||||
packaging
|
matrix_utils==0.6.2
|
||||||
pandas
|
mdurl==0.1.2
|
||||||
peewee
|
morefs==0.2.2
|
||||||
Pint
|
mrio-common-metadata==0.2.1
|
||||||
platformdirs
|
murmurhash==1.0.13
|
||||||
preshed
|
numba==0.62.0
|
||||||
prettytable
|
numpy==2.3.3
|
||||||
pydantic
|
openai==1.99.9
|
||||||
pydantic-settings
|
openpyxl==3.1.5
|
||||||
pydantic_core
|
ordered-set==4.1.0
|
||||||
pyecospold
|
packaging==25.0
|
||||||
Pygments
|
pandas==2.3.2
|
||||||
PyJWT
|
peewee==3.18.2
|
||||||
PyMuPDF
|
pillow==11.3.0
|
||||||
pyparsing
|
Pint==0.25
|
||||||
PyPrind
|
platformdirs==4.4.0
|
||||||
python-dateutil
|
pluggy==1.6.0
|
||||||
python-dotenv
|
polib==1.2.0
|
||||||
python-json-logger
|
preshed==3.0.10
|
||||||
pytz
|
prettytable==3.16.0
|
||||||
pyxlsb
|
prompt_toolkit==3.0.52
|
||||||
PyYAML
|
psycopg2-binary==2.9.11
|
||||||
randonneur
|
pycountry==24.6.1
|
||||||
randonneur_data
|
pydantic==2.11.9
|
||||||
RapidFuzz
|
pydantic-settings==2.10.1
|
||||||
rdflib
|
pydantic_core==2.33.2
|
||||||
referencing
|
pyecospold==4.0.0
|
||||||
requests
|
Pygments==2.19.2
|
||||||
rfc3986
|
PyJWT==2.10.1
|
||||||
rich
|
PyMuPDF==1.26.4
|
||||||
rpds-py
|
pyparsing==3.2.5
|
||||||
s3transfer
|
PyPDF2==3.0.1
|
||||||
scipy
|
PyPrind==2.11.3
|
||||||
shellingham
|
pytest==8.3.4
|
||||||
six
|
pytest-django==4.11.1
|
||||||
smart-open
|
python-dateutil==2.9.0.post0
|
||||||
snowflake-id
|
python-docx==1.2.0
|
||||||
spacy
|
python-dotenv==1.0.1
|
||||||
spacy-legacy
|
python-json-logger==3.3.0
|
||||||
spacy-loggers
|
pytz==2025.2
|
||||||
SPARQLWrapper
|
pyxlsb==1.0.10
|
||||||
sparse
|
PyYAML==6.0.2
|
||||||
SQLAlchemy
|
randonneur==0.6.2
|
||||||
sqlparse
|
randonneur_data==0.6
|
||||||
srsly
|
RapidFuzz==3.14.1
|
||||||
stats_arrays
|
rdflib==7.2.1
|
||||||
structlog
|
redis==3.5.3
|
||||||
tableschema
|
referencing==0.36.2
|
||||||
thinc
|
requests==2.32.3
|
||||||
toolz
|
responses==0.25.8
|
||||||
tqdm
|
rfc3986==2.0.0
|
||||||
typer
|
rich==14.1.0
|
||||||
typing-inspection
|
rpds-py==0.27.1
|
||||||
typing_extensions
|
s3transfer==0.14.0
|
||||||
tzdata
|
scipy==1.16.2
|
||||||
unicodecsv
|
setuptools==80.9.0
|
||||||
urllib3
|
setuptools-scm==8.1.0
|
||||||
voluptuous
|
shellingham==1.5.4
|
||||||
wasabi
|
six==1.17.0
|
||||||
wcwidth
|
smart_open==7.3.1
|
||||||
weasel
|
sniffio==1.3.1
|
||||||
wrapt
|
snowflake-id==1.0.2
|
||||||
wurst
|
spacy==3.8.7
|
||||||
xlrd
|
spacy-legacy==3.0.12
|
||||||
XlsxWriter
|
spacy-loggers==1.0.5
|
||||||
celery[redis]
|
SPARQLWrapper==2.0.0
|
||||||
redis
|
sparse==0.17.0
|
||||||
sentence-transformers
|
SQLAlchemy==2.0.43
|
||||||
torch
|
sqlparse==0.5.3
|
||||||
pdfplumber
|
srsly==2.5.1
|
||||||
python-docx
|
stats_arrays==0.7
|
||||||
PyMuPDF
|
structlog==25.4.0
|
||||||
pytesseract
|
tableschema==1.21.0
|
||||||
Pillow
|
tenacity==9.0.0
|
||||||
python-dotenv
|
thinc==8.3.6
|
||||||
django-countries
|
tomli==2.2.1
|
||||||
django-q2
|
toolz==1.0.0
|
||||||
|
tqdm==4.67.1
|
||||||
|
typer==0.19.2
|
||||||
|
types-python-dateutil==2.9.0.20251008
|
||||||
|
typing-inspection==0.4.1
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
tzdata==2025.2
|
||||||
|
unicodecsv==0.14.1
|
||||||
|
urllib3==2.5.0
|
||||||
|
vine==5.1.0
|
||||||
|
voluptuous==0.15.2
|
||||||
|
wasabi==1.1.3
|
||||||
|
wcwidth==0.2.14
|
||||||
|
weasel==0.4.1
|
||||||
|
webencodings==0.5.1
|
||||||
|
wheel==0.45.1
|
||||||
|
wrapt==1.17.3
|
||||||
|
wurst==0.4
|
||||||
|
xlrd==2.0.2
|
||||||
|
xlsxwriter==3.2.9
|
||||||
|
|||||||
372
templates/people/delete_person.html
Normal file
372
templates/people/delete_person.html
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Delete Person" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block customCSS %}
|
||||||
|
<style>
|
||||||
|
/* KAAT-S UI Variables */
|
||||||
|
:root {
|
||||||
|
--kaauh-teal: #00636e;
|
||||||
|
--kaauh-teal-dark: #004a53;
|
||||||
|
--kaauh-border: #eaeff3;
|
||||||
|
--kaauh-primary-text: #343a40;
|
||||||
|
--kaauh-danger: #dc3545;
|
||||||
|
--kaauh-warning: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Container & Card Styling */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning Section */
|
||||||
|
.warning-section {
|
||||||
|
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
||||||
|
border: 1px solid #ffeeba;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
color: var(--kaauh-warning);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-title {
|
||||||
|
color: #856404;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
color: #856404;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Person Info Card */
|
||||||
|
.person-info {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--kaauh-primary-text);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styling */
|
||||||
|
.btn-danger {
|
||||||
|
background-color: var(--kaauh-danger);
|
||||||
|
border-color: var(--kaauh-danger);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consequence List */
|
||||||
|
.consequence-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li i {
|
||||||
|
color: var(--kaauh-danger);
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Person Profile Image */
|
||||||
|
.person-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px solid var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{% trans "Delete Person" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "You are about to delete a person record. This action cannot be undone." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'person_detail' object.slug %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Person" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Warning Section -->
|
||||||
|
<div class="warning-section">
|
||||||
|
<div class="warning-icon">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||||
|
<p class="warning-text">
|
||||||
|
{% trans "Deleting this person will permanently remove all associated data. Please review the information below carefully before proceeding." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Person Information -->
|
||||||
|
<div class="card kaauh-card mb-4">
|
||||||
|
<div class="card-header bg-white border-bottom">
|
||||||
|
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||||
|
<i class="fas fa-user me-2"></i>
|
||||||
|
{% trans "Person to be Deleted" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="person-info">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
{% if object.profile_image %}
|
||||||
|
<img src="{{ object.profile_image.url }}" alt="{{ object.get_full_name }}" class="person-avatar me-3">
|
||||||
|
{% else %}
|
||||||
|
<div class="avatar-placeholder me-3">
|
||||||
|
<i class="fas fa-user text-muted fa-2x"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-1">{{ object.get_full_name }}</h4>
|
||||||
|
{% if object.email %}
|
||||||
|
<p class="text-muted mb-0">{{ object.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if object.phone %}
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-phone"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Phone" %}</div>
|
||||||
|
<div class="info-value">{{ object.phone }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-calendar"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Created On" %}</div>
|
||||||
|
<div class="info-value">{{ object.created_at|date:"F d, Y" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if object.nationality %}
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Nationality" %}</div>
|
||||||
|
<div class="info-value">{{ object.nationality }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if object.gender %}
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-venus-mars"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Gender" %}</div>
|
||||||
|
<div class="info-value">{{ object.get_gender_display }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Consequences -->
|
||||||
|
<div class="card kaauh-card mb-4">
|
||||||
|
<div class="card-header bg-white border-bottom">
|
||||||
|
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
{% trans "What will happen when you delete this person?" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="consequence-list">
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "The person profile and all personal information will be permanently deleted" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "All associated applications and documents will be removed" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "Any interview schedules and history will be deleted" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "All related data and records will be lost" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "This action cannot be undone under any circumstances" %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Form -->
|
||||||
|
<div class="card kaauh-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" id="deleteForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||||
|
<label class="form-check-label" for="confirm_delete">
|
||||||
|
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this person." %}</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="{% url 'person_detail' object.slug %}" class="btn btn-secondary btn-lg">
|
||||||
|
<i class="fas fa-times me-2"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-danger btn-lg"
|
||||||
|
id="deleteButton"
|
||||||
|
disabled>
|
||||||
|
<i class="fas fa-trash me-2"></i>
|
||||||
|
{% trans "Delete Person Permanently" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const confirmDeleteCheckbox = document.getElementById('confirm_delete');
|
||||||
|
const deleteButton = document.getElementById('deleteButton');
|
||||||
|
const deleteForm = document.getElementById('deleteForm');
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||||
|
deleteButton.disabled = !checkboxChecked;
|
||||||
|
|
||||||
|
if (checkboxChecked) {
|
||||||
|
deleteButton.classList.remove('btn-secondary');
|
||||||
|
deleteButton.classList.add('btn-danger');
|
||||||
|
} else {
|
||||||
|
deleteButton.classList.remove('btn-danger');
|
||||||
|
deleteButton.classList.add('btn-secondary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||||
|
|
||||||
|
// Add confirmation before final submission
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
60
templates/people/person_confirm_delete.html
Normal file
60
templates/people/person_confirm_delete.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Delete Person" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card border-danger">
|
||||||
|
<div class="card-header bg-danger text-white">
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{% trans "Confirm Deletion" %}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h5 class="alert-heading">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{% trans "Warning: This action cannot be undone!" %}
|
||||||
|
</h5>
|
||||||
|
<p class="mb-0">
|
||||||
|
{% trans "You are about to permanently delete this person and all associated data." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
{% if person.profile_image %}
|
||||||
|
<img src="{{ person.profile_image.url }}" alt="{{ person.get_full_name }}"
|
||||||
|
class="rounded-circle mb-3" style="width: 100px; height: 100px; object-fit: cover;">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center mb-3"
|
||||||
|
style="width: 100px; height: 100px;">
|
||||||
|
<i class="fas fa-user text-muted fa-2x"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<h5>{{ person.get_full_name }}</h5>
|
||||||
|
{% if person.email %}
|
||||||
|
<p class="text-muted mb-0">{{ person.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="{% url 'person_update' person.slug %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i> {% trans "Delete Person" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -163,7 +163,7 @@
|
|||||||
<div class="card mb-4 shadow-sm no-hover">
|
<div class="card mb-4 shadow-sm no-hover">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
@ -213,8 +213,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% if people_list %}
|
{% if people_list %}
|
||||||
<div id="person-list">
|
<div id="person-list">
|
||||||
<!-- View Switcher -->
|
<!-- View Switcher -->
|
||||||
@ -287,13 +287,13 @@
|
|||||||
class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-outline-danger"
|
{% comment %} <button type="button" class="btn btn-outline-danger"
|
||||||
title="{% trans 'Delete' %}"
|
title="{% trans 'Delete' %}"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||||
data-delete-url="{% url 'person_delete' person.slug %}"
|
data-delete-url="{% url 'person_delete' person.slug %}"
|
||||||
data-item-name="{{ person.get_full_name }}">
|
data-item-name="{{ person.get_full_name }}">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button> {% endcomment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -194,6 +194,9 @@
|
|||||||
<a href="{% url 'person_detail' person.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'person_detail' person.slug %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'person_delete' person.slug %}" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||||
|
</a>
|
||||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -1,217 +1,478 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}{{ title }} - ATS{% endblock %}
|
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block customCSS %}
|
||||||
|
<style>
|
||||||
|
/* UI Variables for the KAAT-S Theme */
|
||||||
|
:root {
|
||||||
|
--kaauh-teal: #00636e;
|
||||||
|
--kaauh-teal-dark: #004a53;
|
||||||
|
--kaauh-border: #eaeff3;
|
||||||
|
--kaauh-gray-light: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Container Styling */
|
||||||
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Styling */
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Action Button Style */
|
||||||
|
.btn-main-action {
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secondary Button Style */
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Field Styling */
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select:focus {
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumb Styling */
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item + .breadcrumb-item::before {
|
||||||
|
content: ">";
|
||||||
|
color: var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Styling */
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading State */
|
||||||
|
.btn.loading {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.loading::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: auto;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Current Profile Section */
|
||||||
|
.current-profile {
|
||||||
|
background-color: var(--kaauh-gray-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-profile h6 {
|
||||||
|
color: var(--kaauh-teal-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="container-fluid py-4">
|
||||||
<!-- Header -->
|
<div class="form-container">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Breadcrumb Navigation -->
|
||||||
<div>
|
<nav aria-label="breadcrumb">
|
||||||
<h1 class="h3 mb-1">{{ title }}</h1>
|
<ol class="breadcrumb">
|
||||||
<p class="text-muted mb-0">
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'agency_list' %}" class="text-decoration-none text-secondary">
|
||||||
|
<i class="fas fa-building me-1"></i> {% trans "Agencies" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% if agency %}
|
{% if agency %}
|
||||||
{% trans "Update the hiring agency information below." %}
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'agency_detail' agency.slug %}" class="text-decoration-none text-secondary">
|
||||||
|
{{ agency.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page"
|
||||||
|
style="
|
||||||
|
color: #F43B5E; /* Rosy Accent Color */
|
||||||
|
font-weight: 600;">{% trans "Update" %}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Fill in the details to add a new hiring agency." %}
|
<li class="breadcrumb-item active" aria-current="page"
|
||||||
|
style="
|
||||||
|
color: #F43B5E; /* Rosy Accent Color */
|
||||||
|
font-weight: 600;">{% trans "Create" %}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
|
<i class="fas fa-building me-2"></i> {{ title }}
|
||||||
|
</h1>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
{% if agency %}
|
||||||
|
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'agency_delete' agency.slug %}" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'agency_list' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'agency_list' %}" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Agencies" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form -->
|
{% if agency %}
|
||||||
<div class="row">
|
<!-- Current Agency Info -->
|
||||||
<div class="col-lg-8">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card">
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<div class="current-profile">
|
||||||
{% if form.non_field_errors %}
|
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="d-flex align-items-center">
|
||||||
<h5 class="alert-heading">
|
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<i class="fas fa-building text-muted"></i>
|
||||||
{% trans "Please correct the errors below:" %}
|
|
||||||
</h5>
|
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
<p class="mb-0">{{ error }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<div>
|
||||||
|
<h5 class="mb-1">{{ agency.name }}</h5>
|
||||||
<form method="post" novalidate>
|
{% if agency.contact_person %}
|
||||||
{% csrf_token %}
|
<p class="text-muted mb-0">{% trans "Contact" %}: {{ agency.contact_person }}</p>
|
||||||
|
|
||||||
<!-- Name -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
|
||||||
{{ form.name.label }} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.name }}
|
|
||||||
{% if form.name.errors %}
|
|
||||||
{% for error in form.name.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.name.help_text %}
|
{% if agency.email %}
|
||||||
<div class="form-text">{{ form.name.help_text }}</div>
|
<p class="text-muted mb-0">{{ agency.email }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
||||||
|
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Contact Person and Phone -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.contact_person.id_for_label }}" class="form-label">
|
|
||||||
{{ form.contact_person.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.contact_person }}
|
|
||||||
{% if form.contact_person.errors %}
|
|
||||||
{% for error in form.contact_person.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.contact_person.help_text %}
|
|
||||||
<div class="form-text">{{ form.contact_person.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
|
||||||
{{ form.phone.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.phone }}
|
|
||||||
{% if form.phone.errors %}
|
|
||||||
{% for error in form.phone.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.phone.help_text %}
|
|
||||||
<div class="form-text">{{ form.phone.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email and Website -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.email.id_for_label }}" class="form-label">
|
|
||||||
{{ form.email.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.email }}
|
|
||||||
{% if form.email.errors %}
|
|
||||||
{% for error in form.email.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.email.help_text %}
|
|
||||||
<div class="form-text">{{ form.email.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.website.id_for_label }}" class="form-label">
|
|
||||||
{{ form.website.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.website }}
|
|
||||||
{% if form.website.errors %}
|
|
||||||
{% for error in form.website.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.website.help_text %}
|
|
||||||
<div class="form-text">{{ form.website.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Address -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.address.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.address }}
|
|
||||||
{% if form.address.errors %}
|
|
||||||
{% for error in form.address.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.address.help_text %}
|
|
||||||
<div class="form-text">{{ form.address.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Country and City -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.country.id_for_label }}" class="form-label">
|
|
||||||
{{ form.country.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.country }}
|
|
||||||
{% if form.country.errors %}
|
|
||||||
{% for error in form.country.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.country.help_text %}
|
|
||||||
<div class="form-text">{{ form.country.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.city.id_for_label }}" class="form-label">
|
|
||||||
{{ form.city.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.city }}
|
|
||||||
{% if form.city.errors %}
|
|
||||||
{% for error in form.city.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.city.help_text %}
|
|
||||||
<div class="form-text">{{ form.city.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
|
||||||
{{ form.description.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.description }}
|
|
||||||
{% if form.description.errors %}
|
|
||||||
{% for error in form.description.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.description.help_text %}
|
|
||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form Actions -->
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<a href="{% url 'agency_list' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
|
||||||
</a>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Form Card -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h5 class="alert-heading">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||||
|
</h5>
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p class="mb-0">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" novalidate id="agency-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.name.id_for_label }}" class="form-label">
|
||||||
|
{{ form.name.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.name|add_class:"form-control" }}
|
||||||
|
{% if form.name.errors %}
|
||||||
|
{% for error in form.name.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.name.help_text %}
|
||||||
|
<div class="form-text">{{ form.name.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Person and Phone -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.contact_person.id_for_label }}" class="form-label">
|
||||||
|
{{ form.contact_person.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.contact_person|add_class:"form-control" }}
|
||||||
|
{% if form.contact_person.errors %}
|
||||||
|
{% for error in form.contact_person.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.contact_person.help_text %}
|
||||||
|
<div class="form-text">{{ form.contact_person.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||||
|
{{ form.phone.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.phone|add_class:"form-control" }}
|
||||||
|
{% if form.phone.errors %}
|
||||||
|
{% for error in form.phone.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.phone.help_text %}
|
||||||
|
<div class="form-text">{{ form.phone.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email and Website -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.email.id_for_label }}" class="form-label">
|
||||||
|
{{ form.email.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.email|add_class:"form-control" }}
|
||||||
|
{% if form.email.errors %}
|
||||||
|
{% for error in form.email.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.email.help_text %}
|
||||||
|
<div class="form-text">{{ form.email.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.website.id_for_label }}" class="form-label">
|
||||||
|
{{ form.website.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.website|add_class:"form-control" }}
|
||||||
|
{% if form.website.errors %}
|
||||||
|
{% for error in form.website.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.website.help_text %}
|
||||||
|
<div class="form-text">{{ form.website.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.address.id_for_label }}" class="form-label">
|
||||||
|
{{ form.address.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.address|add_class:"form-control" }}
|
||||||
|
{% if form.address.errors %}
|
||||||
|
{% for error in form.address.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.address.help_text %}
|
||||||
|
<div class="form-text">{{ form.address.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Country and City -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.country.id_for_label }}" class="form-label">
|
||||||
|
{{ form.country.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.country|add_class:"form-control" }}
|
||||||
|
{% if form.country.errors %}
|
||||||
|
{% for error in form.country.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.country.help_text %}
|
||||||
|
<div class="form-text">{{ form.country.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label for="{{ form.city.id_for_label }}" class="form-label">
|
||||||
|
{{ form.city.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.city|add_class:"form-control" }}
|
||||||
|
{% if form.city.errors %}
|
||||||
|
{% for error in form.city.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.city.help_text %}
|
||||||
|
<div class="form-text">{{ form.city.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="{{ form.description.id_for_label }}" class="form-label">
|
||||||
|
{{ form.description.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.description|add_class:"form-control" }}
|
||||||
|
{% if form.description.errors %}
|
||||||
|
{% for error in form.description.errors %}
|
||||||
|
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.description.help_text %}
|
||||||
|
<div class="form-text">{{ form.description.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button form="agency-form" type="submit" class="btn btn-main-action">
|
||||||
|
<i class="fas fa-save me-1"></i> {{ button_text }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Add Bootstrap classes to form fields
|
// Form Validation
|
||||||
const formFields = document.querySelectorAll('input[type="text"], input[type="email"], input[type="url"], input[type="tel"], textarea, select');
|
const form = document.getElementById('agency-form');
|
||||||
formFields.forEach(function(field) {
|
if (form) {
|
||||||
field.classList.add('form-control');
|
form.addEventListener('submit', function(e) {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
submitBtn.classList.add('loading');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
const name = document.getElementById('id_name');
|
||||||
|
if (name && !name.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Agency name is required." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = document.getElementById('id_email');
|
||||||
|
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Please enter a valid email address." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const website = document.getElementById('id_website');
|
||||||
|
if (website && website.value.trim() && !isValidURL(website.value.trim())) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Please enter a valid website URL." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email validation helper
|
||||||
|
function isValidEmail(email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL validation helper
|
||||||
|
function isValidURL(url) {
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn before leaving if changes are made
|
||||||
|
let formChanged = false;
|
||||||
|
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
|
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
formChanged = true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
if (formChanged) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
||||||
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function() {
|
||||||
|
formChanged = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,43 +1,51 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Update Candidate - {{ block.super }}{% endblock %}
|
{% block title %}Update {{ object.name }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
/* ================================================= */
|
/* UI Variables for the KAAT-S Theme */
|
||||||
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
:root {
|
||||||
--kaauh-teal: #00636e;
|
--kaauh-teal: #00636e;
|
||||||
--kaauh-teal-dark: #004a53;
|
--kaauh-teal-dark: #004a53;
|
||||||
--kaauh-border: #eaeff3;
|
--kaauh-border: #eaeff3;
|
||||||
--kaauh-primary-text: #343a40;
|
--kaauh-gray-light: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
/* Form Container Styling */
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Styling */
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
/* Main Action Button Style */
|
/* Main Action Button Style */
|
||||||
.btn-main-action, .btn-primary {
|
.btn-main-action {
|
||||||
background-color: var(--kaauh-teal);
|
background-color: var(--kaauh-teal);
|
||||||
border-color: var(--kaauh-teal);
|
border-color: var(--kaauh-teal);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.4rem;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
}
|
}
|
||||||
.btn-main-action:hover, .btn-primary:hover {
|
|
||||||
|
.btn-main-action:hover {
|
||||||
background-color: var(--kaauh-teal-dark);
|
background-color: var(--kaauh-teal-dark);
|
||||||
border-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);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Outlined Button Styles */
|
/* Secondary Button Style */
|
||||||
.btn-outline-secondary {
|
.btn-outline-secondary {
|
||||||
color: var(--kaauh-teal-dark);
|
color: var(--kaauh-teal-dark);
|
||||||
border-color: var(--kaauh-teal);
|
border-color: var(--kaauh-teal);
|
||||||
@ -48,94 +56,308 @@
|
|||||||
border-color: var(--kaauh-teal-dark);
|
border-color: var(--kaauh-teal-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card enhancements */
|
/* Form Field Styling */
|
||||||
.card {
|
.form-control:focus {
|
||||||
border: 1px solid var(--kaauh-border);
|
border-color: var(--kaauh-teal);
|
||||||
border-radius: 0.75rem;
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colored Header Card */
|
.form-select:focus {
|
||||||
.candidate-header-card {
|
border-color: var(--kaauh-teal);
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
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;
|
/* Profile Image Upload Styling */
|
||||||
margin: 0;
|
.profile-image-upload {
|
||||||
font-size: 1.8rem;
|
border: 2px dashed var(--kaauh-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.heroicon {
|
|
||||||
width: 1.25rem;
|
.profile-image-upload:hover {
|
||||||
height: 1.25rem;
|
border-color: var(--kaauh-teal);
|
||||||
vertical-align: text-bottom;
|
background-color: var(--kaauh-gray-light);
|
||||||
stroke: currentColor;
|
}
|
||||||
margin-right: 0.5rem;
|
|
||||||
|
.profile-image-preview {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid var(--kaauh-teal);
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-image {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--kaauh-teal);
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumb Styling */
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item + .breadcrumb-item::before {
|
||||||
|
content: ">";
|
||||||
|
color: var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Styling */
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading State */
|
||||||
|
.btn.loading {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.loading::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: auto;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Current Profile Section */
|
||||||
|
.current-profile {
|
||||||
|
background-color: var(--kaauh-gray-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-profile h6 {
|
||||||
|
color: var(--kaauh-teal-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
|
<div class="form-container">
|
||||||
<div class="card mb-4">
|
<!-- Breadcrumb Navigation -->
|
||||||
<div class="candidate-header-card">
|
<nav aria-label="breadcrumb">
|
||||||
<div class="d-flex justify-content-between align-items-start flex-wrap">
|
<ol class="breadcrumb">
|
||||||
<div class="flex-grow-1">
|
<li class="breadcrumb-item">
|
||||||
<h1 class="h3 mb-1">
|
<a href="{% url 'application_list' %}" class="text-decoration-none text-secondary">
|
||||||
<i class="fas fa-user-edit"></i>
|
<i class="fas fa-users me-1"></i> {% trans "Applications" %}
|
||||||
{% trans "Update Candidate:" %} {{ object.name }}
|
|
||||||
</h1>
|
|
||||||
<p class="text-white opacity-75 mb-0">{% trans "Edit candidate information and details" %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2 mt-1">
|
|
||||||
<a href="{% url 'application_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
|
|
||||||
<i class="fas fa-arrow-left"></i>
|
|
||||||
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
|
|
||||||
</a>
|
</a>
|
||||||
{% if object.slug %}
|
</li>
|
||||||
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-light btn-sm" title="{% trans 'View Candidate' %}">
|
<li class="breadcrumb-item">
|
||||||
<i class="fas fa-eye"></i>
|
<a href="{% url 'application_detail' object.slug %}" class="text-decoration-none text-secondary">
|
||||||
<span class="d-none d-sm-inline">{% trans "View" %}</span>
|
{{ object.name }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page"
|
||||||
|
style="
|
||||||
|
color: #F43B5E; /* Rosy Accent Color */
|
||||||
|
font-weight: 600;">{% trans "Update" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
|
<i class="fas fa-user-edit me-2"></i> {% trans "Update Application" %}
|
||||||
|
</h1>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'application_delete' object.slug %}" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Profile Info -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="current-profile">
|
||||||
|
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
{% if object.profile_image %}
|
||||||
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
||||||
|
class="current-image">
|
||||||
|
{% else %}
|
||||||
|
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
||||||
|
<i class="fas fa-user text-muted"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-1">{{ object.name }}</h5>
|
||||||
|
{% if object.email %}
|
||||||
|
<p class="text-muted mb-0">{{ object.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
||||||
|
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<!-- Form Card -->
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="card shadow-sm">
|
||||||
<h2 class="h5 mb-0 text-primary">
|
<div class="card-body p-4">
|
||||||
<i class="fas fa-file-alt me-1"></i>
|
{% if form.non_field_errors %}
|
||||||
{% trans "Candidate Form" %}
|
<div class="alert alert-danger" role="alert">
|
||||||
</h2>
|
<h5 class="alert-heading">
|
||||||
</div>
|
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||||
<div class="card-body">
|
</h5>
|
||||||
<form method="post" enctype="multipart/form-data">
|
{% for error in form.non_field_errors %}
|
||||||
{% csrf_token %}
|
<p class="mb-0">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
{# Use Crispy Forms to render fields. The two-column layout is applied to the main form content #}
|
</div>
|
||||||
<div class="row g-4">
|
{% endif %}
|
||||||
{% for field in form %}
|
|
||||||
<div class="col-md-6">
|
{% if messages %}
|
||||||
{{ field|as_crispy_field }}
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
{% endif %}
|
||||||
|
|
||||||
<hr class="mt-4 mb-4">
|
<form method="post" action="{% url 'candidate_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
||||||
<button class="btn btn-main-action" type="submit">
|
{% csrf_token %}
|
||||||
<i class="fas fa-save me-1"></i>
|
{{form|crispy}}
|
||||||
{% trans "Update Candidate" %}
|
</form>
|
||||||
</button>
|
<div class="d-flex gap-2">
|
||||||
</form>
|
<button form="candidate-form" type="submit" class="btn btn-main-action">
|
||||||
|
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Profile Image Preview
|
||||||
|
const profileImageInput = document.getElementById('id_profile_image');
|
||||||
|
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||||
|
const originalImage = imagePreviewContainer ? imagePreviewContainer.innerHTML : '';
|
||||||
|
|
||||||
|
if (profileImageInput) {
|
||||||
|
profileImageInput.addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file && file.type.startsWith('image/')) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
if (imagePreviewContainer) {
|
||||||
|
imagePreviewContainer.innerHTML = `
|
||||||
|
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
||||||
|
<h5 class="text-muted mt-3">${file.name}</h5>
|
||||||
|
<p class="text-muted small">{% trans "New photo selected" %}</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
} else if (!file && imagePreviewContainer) {
|
||||||
|
// Reset to original if no file selected
|
||||||
|
imagePreviewContainer.innerHTML = originalImage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form Validation
|
||||||
|
const form = document.getElementById('candidate-form');
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
submitBtn.classList.add('loading');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
const name = document.getElementById('id_name');
|
||||||
|
if (name && !name.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Name is required." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = document.getElementById('id_email');
|
||||||
|
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Please enter a valid email address." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email validation helper
|
||||||
|
function isValidEmail(email) {
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
return emailRegex.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn before leaving if changes are made
|
||||||
|
let formChanged = false;
|
||||||
|
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
|
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
formChanged = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
if (formChanged) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
||||||
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function() {
|
||||||
|
formChanged = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@ -332,12 +332,12 @@
|
|||||||
<a href="{% url 'application_update' candidate.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
<a href="{% url 'application_update' candidate.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
|
{% comment %} <button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||||
data-delete-url="{% url 'application_delete' candidate.slug %}"
|
data-delete-url="{% url 'application_delete' candidate.slug %}"
|
||||||
data-item-name="{{ candidate.name }}">
|
data-item-name="{{ candidate.name }}">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button> {% endcomment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
386
templates/recruitment/candidate_delete.html
Normal file
386
templates/recruitment/candidate_delete.html
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Delete Candidate" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block customCSS %}
|
||||||
|
<style>
|
||||||
|
/* KAAT-S UI Variables */
|
||||||
|
:root {
|
||||||
|
--kaauh-teal: #00636e;
|
||||||
|
--kaauh-teal-dark: #004a53;
|
||||||
|
--kaauh-border: #eaeff3;
|
||||||
|
--kaauh-primary-text: #343a40;
|
||||||
|
--kaauh-danger: #dc3545;
|
||||||
|
--kaauh-warning: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Container & Card Styling */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning Section */
|
||||||
|
.warning-section {
|
||||||
|
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
||||||
|
border: 1px solid #ffeeba;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
color: var(--kaauh-warning);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-title {
|
||||||
|
color: #856404;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
color: #856404;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Candidate Info Card */
|
||||||
|
.candidate-info {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--kaauh-primary-text);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styling */
|
||||||
|
.btn-danger {
|
||||||
|
background-color: var(--kaauh-danger);
|
||||||
|
border-color: var(--kaauh-danger);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
border-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Consequence List */
|
||||||
|
.consequence-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consequence-list li i {
|
||||||
|
color: var(--kaauh-danger);
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Candidate Profile Image */
|
||||||
|
.candidate-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 3px solid var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px solid var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
{% trans "Delete Candidate" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{% trans "You are about to delete a candidate application. This action cannot be undone." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Candidate" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Warning Section -->
|
||||||
|
<div class="warning-section">
|
||||||
|
<div class="warning-icon">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||||
|
<p class="warning-text">
|
||||||
|
{% trans "Deleting this candidate will permanently remove all associated data. Please review the information below carefully before proceeding." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Candidate Information -->
|
||||||
|
<div class="card kaauh-card mb-4">
|
||||||
|
<div class="card-header bg-white border-bottom">
|
||||||
|
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||||
|
<i class="fas fa-user me-2"></i>
|
||||||
|
{% trans "Candidate to be Deleted" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="candidate-info">
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
{% if object.profile_image %}
|
||||||
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}" class="candidate-avatar me-3">
|
||||||
|
{% else %}
|
||||||
|
<div class="avatar-placeholder me-3">
|
||||||
|
<i class="fas fa-user text-muted fa-2x"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-1">{{ object.name }}</h4>
|
||||||
|
{% if object.email %}
|
||||||
|
<p class="text-muted mb-0">{{ object.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-briefcase"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Position Applied" %}</div>
|
||||||
|
<div class="info-value">
|
||||||
|
{% if object.job_posting %}
|
||||||
|
{{ object.job_posting.title }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Not specified" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if object.phone %}
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-phone"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Phone" %}</div>
|
||||||
|
<div class="info-value">{{ object.phone }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-calendar"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Applied On" %}</div>
|
||||||
|
<div class="info-value">{{ object.created_at|date:"F d, Y" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-icon">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">{% trans "Status" %}</div>
|
||||||
|
<div class="info-value">
|
||||||
|
{% if object.status %}
|
||||||
|
<span class="badge bg-info">{{ object.get_status_display }}</span>
|
||||||
|
{% else %}
|
||||||
|
{% trans "Not specified" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Consequences -->
|
||||||
|
<div class="card kaauh-card mb-4">
|
||||||
|
<div class="card-header bg-white border-bottom">
|
||||||
|
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||||
|
<i class="fas fa-list me-2"></i>
|
||||||
|
{% trans "What will happen when you delete this candidate?" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="consequence-list">
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "The candidate profile and all personal information will be permanently deleted" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "All application data and documents will be removed" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "Interview schedules and history will be deleted" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "Any associated notes and communications will be lost" %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-times-circle"></i>
|
||||||
|
{% trans "This action cannot be undone under any circumstances" %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmation Form -->
|
||||||
|
<div class="card kaauh-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" id="deleteForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||||
|
<label class="form-check-label" for="confirm_delete">
|
||||||
|
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this candidate." %}</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary btn-lg">
|
||||||
|
<i class="fas fa-times me-2"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-danger btn-lg"
|
||||||
|
id="deleteButton"
|
||||||
|
disabled>
|
||||||
|
<i class="fas fa-trash me-2"></i>
|
||||||
|
{% trans "Delete Candidate Permanently" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const confirmDeleteCheckbox = document.getElementById('confirm_delete');
|
||||||
|
const deleteButton = document.getElementById('deleteButton');
|
||||||
|
const deleteForm = document.getElementById('deleteForm');
|
||||||
|
|
||||||
|
function validateForm() {
|
||||||
|
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||||
|
deleteButton.disabled = !checkboxChecked;
|
||||||
|
|
||||||
|
if (checkboxChecked) {
|
||||||
|
deleteButton.classList.remove('btn-secondary');
|
||||||
|
deleteButton.classList.add('btn-danger');
|
||||||
|
} else {
|
||||||
|
deleteButton.classList.remove('btn-danger');
|
||||||
|
deleteButton.classList.add('btn-secondary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||||
|
|
||||||
|
// Add confirmation before final submission
|
||||||
|
/*deleteForm.addEventListener('submit', function(e) {
|
||||||
|
const confirmMessage = "{% trans 'Are you absolutely sure you want to delete this candidate? This action cannot be undone.' %}";
|
||||||
|
if (!confirm(confirmMessage)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -2,196 +2,409 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block customCSS %}
|
||||||
|
<style>
|
||||||
|
/* UI Variables for the KAAT-S Theme */
|
||||||
|
:root {
|
||||||
|
--kaauh-teal: #00636e;
|
||||||
|
--kaauh-teal-dark: #004a53;
|
||||||
|
--kaauh-border: #eaeff3;
|
||||||
|
--kaauh-gray-light: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Container Styling */
|
||||||
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Styling */
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Action Button Style */
|
||||||
|
.btn-main-action {
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secondary Button Style */
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Field Styling */
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select:focus {
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumb Styling */
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item + .breadcrumb-item::before {
|
||||||
|
content: ">";
|
||||||
|
color: var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert Styling */
|
||||||
|
.alert {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading State */
|
||||||
|
.btn.loading {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.loading::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: auto;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top-color: #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Current Profile Section */
|
||||||
|
.current-profile {
|
||||||
|
background-color: var(--kaauh-gray-light);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-profile h6 {
|
||||||
|
color: var(--kaauh-teal-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-image {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--kaauh-teal);
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid py-4">
|
||||||
<div class="row">
|
<div class="form-container">
|
||||||
<div class="col-12">
|
<!-- Breadcrumb Navigation -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<nav aria-label="breadcrumb">
|
||||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'source_list' %}" class="text-decoration-none text-secondary">
|
||||||
|
<i class="fas fa-plug me-1"></i> {% trans "Sources" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if source %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-secondary">
|
||||||
|
{{ source.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page"
|
||||||
|
style="
|
||||||
|
color: #F43B5E; /* Rosy Accent Color */
|
||||||
|
font-weight: 600;">{% trans "Update" %}</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="breadcrumb-item active" aria-current="page"
|
||||||
|
style="
|
||||||
|
color: #F43B5E; /* Rosy Accent Color */
|
||||||
|
font-weight: 600;">{% trans "Create" %}</li>
|
||||||
|
{% endif %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
|
<i class="fas fa-plug me-2"></i> {{ title }}
|
||||||
|
</h1>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
{% if source %}
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'source_delete' source.pk %}" class="btn btn-danger">
|
||||||
|
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> {% trans "Back to Sources" %}
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
{% if source %}
|
||||||
<div class="card-body">
|
<!-- Current Source Info -->
|
||||||
<form method="post" novalidate>
|
<div class="card shadow-sm mb-4">
|
||||||
{% csrf_token %}
|
<div class="card-body">
|
||||||
|
<div class="current-profile">
|
||||||
|
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
||||||
|
<i class="fas fa-plug text-muted"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-1">{{ source.name }}</h5>
|
||||||
|
{% if source.source_type %}
|
||||||
|
<p class="text-muted mb-0">{% trans "Type" %}: {{ source.get_source_type_display }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if source.ip_address %}
|
||||||
|
<p class="text-muted mb-0">{% trans "IP Address" %}: {{ source.ip_address }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{% trans "Created" %}: {{ source.created_at|date:"d M Y" }} •
|
||||||
|
{% trans "Last Updated" %}: {{ source.updated_at|date:"d M Y" }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
<!-- Form Card -->
|
||||||
<div class="alert alert-danger">
|
<div class="card shadow-sm">
|
||||||
{% for error in form.non_field_errors %}
|
<div class="card-body p-4">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h5 class="alert-heading">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||||
|
</h5>
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p class="mb-0">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" novalidate id="source-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.name.id_for_label }}" class="form-label">
|
||||||
|
{{ form.name.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.name|add_class:"form-control" }}
|
||||||
|
{% if form.name.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.name.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.name.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.source_type.id_for_label }}" class="form-label">
|
||||||
|
{{ form.source_type.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.source_type|add_class:"form-select" }}
|
||||||
|
{% if form.source_type.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.source_type.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.source_type.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
||||||
|
{{ form.ip_address.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.ip_address|add_class:"form-control" }}
|
||||||
|
{% if form.ip_address.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.ip_address.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.ip_address.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.trusted_ips.id_for_label }}" class="form-label">
|
||||||
|
{{ form.trusted_ips.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.trusted_ips|add_class:"form-control" }}
|
||||||
|
{% if form.trusted_ips.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.trusted_ips.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.trusted_ips.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.description.id_for_label }}" class="form-label">
|
||||||
|
{{ form.description.label }}
|
||||||
|
</label>
|
||||||
|
{{ form.description|add_class:"form-control" }}
|
||||||
|
{% if form.description.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.description.errors %}
|
||||||
{{ error }}
|
{{ error }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.description.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
<div class="form-check">
|
||||||
{{ form.name.label }} <span class="text-danger">*</span>
|
{{ form.is_active|add_class:"form-check-input" }}
|
||||||
|
<label for="{{ form.is_active.id_for_label }}" class="form-check-label">
|
||||||
|
{{ form.is_active.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.name|add_class:"form-control" }}
|
|
||||||
{% if form.name.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.name.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.name.help_text }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% if form.is_active.errors %}
|
||||||
<div class="col-md-6">
|
<div class="invalid-feedback d-block">
|
||||||
<div class="mb-3">
|
{% for error in form.is_active.errors %}
|
||||||
<label for="{{ form.source_type.id_for_label }}" class="form-label">
|
{{ error }}
|
||||||
{{ form.source_type.label }} <span class="text-danger">*</span>
|
{% endfor %}
|
||||||
</label>
|
|
||||||
{{ form.source_type }}
|
|
||||||
{% if form.source_type.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.source_type.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.source_type.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.ip_address.label }} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.ip_address|add_class:"form-control" }}
|
|
||||||
{% if form.ip_address.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.ip_address.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.ip_address.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.trusted_ips.label }} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.trusted_ips|add_class:"form-control" }}
|
|
||||||
{% if form.trusted_ips.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.trusted_ips.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.trusted_ips.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
|
||||||
{{ form.description.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.description|add_class:"form-control" }}
|
|
||||||
{% if form.description.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.description.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="form-check">
|
|
||||||
{{ form.is_active|add_class:"form-check-input bg-primary-theme" }}
|
|
||||||
<label for="{{ form.is_active.id_for_label }}" class="form-check-label">
|
|
||||||
{{ form.is_active.label }}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{% if form.is_active.errors %}
|
{% endif %}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="form-text">{{ form.is_active.help_text }}</div>
|
||||||
{% for error in form.is_active.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.is_active.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API Credentials Section -->
|
||||||
<!-- API Credentials Section -->
|
{% if source %}
|
||||||
{% if source %}
|
<div class="card bg-light mb-4">
|
||||||
<div class="card bg-light mb-4">
|
<div class="card-header">
|
||||||
<div class="card-header">
|
<h6 class="mb-0">{% trans "API Credentials" %}</h6>
|
||||||
<h6 class="mb-0">{% trans "API Credentials" %}</h6>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<div class="mb-3">
|
||||||
<div class="mb-3">
|
<label class="form-label">{% trans "API Key" %}</label>
|
||||||
<label class="form-label">{% trans "API Key" %}</label>
|
<div class="input-group">
|
||||||
<div class="input-group">
|
<input type="text" class="form-control" value="{{ source.api_key }}" readonly>
|
||||||
<input type="text" class="form-control" value="{{ source.api_key }}" readonly>
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
hx-post="{% url 'copy_to_clipboard' %}"
|
||||||
hx-post="{% url 'copy_to_clipboard' %}"
|
hx-vals='{"text": "{{ source.api_key }}"}'
|
||||||
hx-vals='{"text": "{{ source.api_key }}"}'
|
title="{% trans 'Copy to clipboard' %}">
|
||||||
title="{% trans 'Copy to clipboard' %}">
|
<i class="fas fa-copy"></i>
|
||||||
<i class="fas fa-copy"></i>
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">{% trans "API Secret" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="password" class="form-control" value="{{ source.api_secret }}" readonly id="api-secret">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" onclick="toggleSecretVisibility()">
|
|
||||||
<i class="fas fa-eye" id="secret-toggle-icon"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
|
||||||
hx-post="{% url 'copy_to_clipboard' %}"
|
|
||||||
hx-vals='{"text": "{{ source.api_secret }}"}'
|
|
||||||
title="{% trans 'Copy to clipboard' %}">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="col-md-6">
|
||||||
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning">
|
<div class="mb-3">
|
||||||
<i class="fas fa-key"></i> {% trans "Generate New Keys" %}
|
<label class="form-label">{% trans "API Secret" %}</label>
|
||||||
</a>
|
<div class="input-group">
|
||||||
|
<input type="password" class="form-control" value="{{ source.api_secret }}" readonly id="api-secret">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="toggleSecretVisibility()">
|
||||||
|
<i class="fas fa-eye" id="secret-toggle-icon"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
|
hx-post="{% url 'copy_to_clipboard' %}"
|
||||||
|
hx-vals='{"text": "{{ source.api_secret }}"}'
|
||||||
|
title="{% trans 'Copy to clipboard' %}">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning">
|
||||||
|
<i class="fas fa-key"></i> {% trans "Generate New Keys" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-times me-1"></i>{% trans "Cancel" %}
|
|
||||||
</a>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Save" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button form="source-form" type="submit" class="btn btn-main-action">
|
||||||
|
<i class="fas fa-save me-1"></i> {% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -200,6 +413,67 @@
|
|||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Form Validation
|
||||||
|
const form = document.getElementById('source-form');
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
submitBtn.classList.add('loading');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
const name = document.getElementById('id_name');
|
||||||
|
if (name && !name.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Source name is required." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipAddress = document.getElementById('id_ip_address');
|
||||||
|
if (ipAddress && ipAddress.value.trim() && !isValidIP(ipAddress.value.trim())) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.classList.remove('loading');
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
alert('{% trans "Please enter a valid IP address." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP validation helper
|
||||||
|
function isValidIP(ip) {
|
||||||
|
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||||
|
return ipRegex.test(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn before leaving if changes are made
|
||||||
|
let formChanged = false;
|
||||||
|
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
|
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
formChanged = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
if (formChanged) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
||||||
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function() {
|
||||||
|
formChanged = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function toggleSecretVisibility() {
|
function toggleSecretVisibility() {
|
||||||
const secretInput = document.getElementById('api-secret');
|
const secretInput = document.getElementById('api-secret');
|
||||||
const toggleIcon = document.getElementById('secret-toggle-icon');
|
const toggleIcon = document.getElementById('secret-toggle-icon');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user