hiring: вакансии и найм, HR-портал
Модель вакансий, флоу рекрутинга и панели HR-портала (заявки/вакансии). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
{% load i18n %}
|
||||
<div id="hr-panel-requests" class="hr-panel"{% if hr_active_tab|default:'employees' != 'requests' %} style="display:none;"{% endif %}>
|
||||
|
||||
{% if hr_request_created %}
|
||||
<div style="margin-bottom:1rem; padding:0.875rem 1rem; border-radius:0.75rem; border:1px solid hsl(152 40% 70%); background:hsl(152 45% 96%); color:var(--color-foreground); font-size:0.875rem; display:none;" aria-hidden="true">
|
||||
{% trans "Заявку подано" %}: <strong>{{ hr_request_created.summary }}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if hr_request_errors %}
|
||||
<div style="margin-bottom:1rem; padding:0.75rem 1rem; border-radius:0.5rem; background:hsl(0 70% 96%); border:1px solid hsl(0 70% 85%); color:hsl(0 55% 35%); font-size:0.8125rem; display:none;" aria-hidden="true">
|
||||
{% for err in hr_request_errors %}<div>{{ err }}</div>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(9rem,1fr)); gap:1rem; margin-bottom:1.25rem;">
|
||||
{% for value, label in hr_request_types %}
|
||||
<button type="button" onclick="hrShowRequestForm('{{ value }}', '{{ label|escapejs }}')"
|
||||
class="hr-request-type-btn" data-request-type="{{ value }}" data-request-label="{{ label }}"
|
||||
style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1.25rem; text-align:center; cursor:pointer;">
|
||||
<div style="width:2.5rem; height:2.5rem; border-radius:0.625rem; background:hsl(205 80% 55% / 0.12); color:hsl(205 80% 45%); display:flex; align-items:center; justify-content:center; margin:0 auto 0.75rem;">
|
||||
{% if value == 'substitution' %}
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||
{% else %}
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="font-size:0.875rem; font-weight:500; color:var(--color-foreground);">{{ label }}</div>
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.25rem;">
|
||||
{% if value == 'substitution' %}{% trans "Открыть запрос" %}{% else %}{% trans "Подать заявку" %}{% endif %}
|
||||
</div>
|
||||
</button>
|
||||
{% empty %}
|
||||
<div style="grid-column:1 / -1; padding:1rem; color:var(--color-muted-foreground); font-size:0.8125rem;">{% trans "Нет доступных типов заявок" %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div id="hr-request-form-wrap" style="{% if hr_request_form_open %}display:block;{% else %}display:none;{% endif %} margin-bottom:1.25rem;">
|
||||
<form hx-post="{% url 'hr_request_create' %}" hx-target="#main-content" hx-indicator="#loader" novalidate
|
||||
style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); overflow:hidden;">
|
||||
{% csrf_token %}
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; justify-content:space-between; align-items:center; gap:0.5rem;">
|
||||
<span style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);"><span id="hr-request-type-label">{% if hr_request_form.type %}{% for value, label in hr_request_types %}{% if hr_request_form.type == value %}{{ label }}{% endif %}{% endfor %}{% else %}{% trans "Новая заявка" %}{% endif %}</span></span>
|
||||
<button type="button" class="btn btn-ghost" style="font-size:0.75rem;" onclick="hrHideRequestForm()">{% trans "Закрыть" %}</button>
|
||||
</div>
|
||||
<input type="hidden" name="request_type" id="hr-request-type" value="{{ hr_request_form.type|default:hr_request_types.0.0|default:'vacation' }}">
|
||||
<div style="padding:1.25rem; display:grid; grid-template-columns:repeat(auto-fit, minmax(10rem, 1fr)); gap:0.75rem; align-items:end;">
|
||||
<div id="hr-request-dates-wrap">
|
||||
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Дата с" %}</label>
|
||||
<input type="date" name="date_from" id="hr-request-date-from" class="input" style="width:100%;" value="{{ hr_request_form.date_from|default:'' }}">
|
||||
</div>
|
||||
<div id="hr-request-date-to-wrap">
|
||||
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Дата по" %}</label>
|
||||
<input type="date" name="date_to" id="hr-request-date-to" class="input" style="width:100%;" value="{{ hr_request_form.date_to|default:'' }}">
|
||||
</div>
|
||||
<div style="grid-column:1 / -1;">
|
||||
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Комментарий" %}</label>
|
||||
<textarea name="note" rows="2" class="input" style="width:100%; resize:vertical;" placeholder="{% if hr_request_form.type == 'substitution' %}{% trans 'Например: смена, причина' %}{% else %}{% trans 'Необязательно' %}{% endif %}">{{ hr_request_form.note|default:'' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:0.875rem 1.25rem; border-top:1px solid var(--color-border); display:flex; justify-content:flex-end;">
|
||||
<button type="submit" class="btn btn-primary">{% trans "Отправить" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if incoming_substitutions %}
|
||||
<div style="margin-bottom:1.25rem; background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); overflow:hidden;">
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border);">
|
||||
<span style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground);">{% trans "Открытые запросы на подмену" %} ({{ incoming_substitutions|length }})</span>
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "Подтвердите, если можете выйти на подмену" %}</div>
|
||||
</div>
|
||||
{% for req in incoming_substitutions %}
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; gap:0.75rem; flex-wrap:wrap;" class="li-hover">
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);">
|
||||
{{ req.employee.last_name }} {{ req.employee.first_name }} — {{ req.summary }}
|
||||
</div>
|
||||
{% if req.pharmacy_place %}
|
||||
<div style="font-size:0.75rem; color:var(--color-foreground); margin-top:0.2rem;">{{ req.pharmacy_place }}</div>
|
||||
{% endif %}
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.125rem;">
|
||||
{% trans "Запрос" %} {{ req.created_at|date:"d.m.Y H:i" }}
|
||||
{% if req.note %} · {{ req.note }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; gap:0.5rem; flex-shrink:0;">
|
||||
<form hx-post="{% url 'hr_substitution_confirm' req.pk %}" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary" style="font-size:0.75rem;">{% trans "Взять подмену" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:1.25rem;">
|
||||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); overflow:hidden;">
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between;">
|
||||
<span style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);">{% trans "Мои заявки" %}</span>
|
||||
</div>
|
||||
<div>
|
||||
{% for req in my_hr_requests %}
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; gap:0.75rem;" class="li-hover">
|
||||
<div style="min-width:0;">
|
||||
<div style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);">{{ req.summary }}</div>
|
||||
{% if req.pharmacy_place %}
|
||||
<div style="font-size:0.75rem; color:var(--color-foreground); margin-top:0.2rem;">{{ req.pharmacy_place }}</div>
|
||||
{% endif %}
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.125rem;">
|
||||
{% trans "Подано" %} {{ req.created_at|date:"d.m.Y" }}
|
||||
{% if req.day_count %} · {{ req.day_count }} {% trans "дн." %}{% endif %}
|
||||
{% if req.request_type == 'substitution' and req.substitute %}
|
||||
· {% trans "Подмена" %}: {{ req.substitute.last_name }} {{ req.substitute.first_name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:0.5rem; flex-shrink:0;">
|
||||
{% if req.status == 'pending' %}
|
||||
<span class="badge badge-warning">{{ req.status_label }}</span>
|
||||
<form hx-post="/hr-requests/{{ req.pk }}/cancel/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-ghost" style="font-size:0.6875rem; padding:0.2rem 0.4rem;">{% trans "Отменить" %}</button>
|
||||
</form>
|
||||
{% elif req.status == 'approved' %}
|
||||
<span class="badge badge-success">{{ req.status_label }}</span>
|
||||
{% elif req.status == 'rejected' %}
|
||||
<span class="badge badge-danger">{{ req.status_label }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-muted">{{ req.status_label }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div style="padding:1.5rem; text-align:center; color:var(--color-muted-foreground); font-size:0.8125rem;">{% trans "Заявок пока нет" %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05); overflow:hidden;">
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border);">
|
||||
<span style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);">{% trans "Баланс отпусков" %} — {{ leave_balance_year }}</span>
|
||||
</div>
|
||||
<div style="padding:1.25rem; display:flex; flex-direction:column; gap:1rem;">
|
||||
{% for row in leave_balance %}
|
||||
<div>
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:0.375rem;">
|
||||
<span style="font-size:0.8125rem; color:var(--color-foreground);">{{ row.label }}</span>
|
||||
<span style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground);">{{ row.used }} / {{ row.entitlement }} {% trans "дн." %}</span>
|
||||
</div>
|
||||
<div class="progress"><div class="progress-bar" style="width:{{ row.used_pct }}%;{% if row.key == 'sick' %} background:hsl(38 80% 45%);{% endif %}"></div></div>
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "Остаток" %}: {{ row.remaining }} {% trans "дн." %}{% if row.pending %} · {% trans "ожидает" %}: {{ row.pending }}{% endif %}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if next_vacation_request %}
|
||||
<div style="padding:0.875rem 1rem; background:var(--color-muted); border-radius:0.625rem; border:1px solid var(--color-border); margin-top:0.25rem;">
|
||||
<div style="font-size:0.75rem; font-weight:600; color:var(--color-foreground); margin-bottom:0.5rem;">{% trans "Следующий отпуск" %}</div>
|
||||
<div style="display:flex; justify-content:space-between; font-size:0.8125rem; gap:0.5rem;">
|
||||
<span style="color:var(--color-muted-foreground);">{{ next_vacation_request.summary }}</span>
|
||||
<span class="badge badge-warning">{{ next_vacation_request.get_status_display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_decide_hr_requests and hr_pending_requests %}
|
||||
<div style="margin-top:1.5rem; background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); overflow:hidden;">
|
||||
<div style="padding:0.875rem 1.25rem; border-bottom:1px solid var(--color-border);">
|
||||
<span style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground);">{% trans "Заявки на рассмотрении" %} ({{ hr_pending_requests|length }})</span>
|
||||
</div>
|
||||
<div style="overflow-x:auto;">
|
||||
<table style="width:100%; border-collapse:collapse; font-size:0.875rem;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid var(--color-border);">
|
||||
<th style="padding:0.75rem 1rem; text-align:left; font-size:0.8125rem; color:var(--color-muted-foreground);">{% trans "Сотрудник" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; font-size:0.8125rem; color:var(--color-muted-foreground);">{% trans "Заявка" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; font-size:0.8125rem; color:var(--color-muted-foreground);">{% trans "Действия" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in hr_pending_requests %}
|
||||
<tr style="border-bottom:1px solid var(--color-border); vertical-align:top;">
|
||||
<td style="padding:0.75rem 1rem;">
|
||||
<div style="font-weight:500;">{{ req.employee.last_name }} {{ req.employee.first_name }}</div>
|
||||
{% if req.pharmacy_place %}
|
||||
<div style="font-size:0.75rem; color:var(--color-foreground); margin-top:0.2rem;">{{ req.pharmacy_place }}</div>
|
||||
{% endif %}
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground);">{{ req.created_at|date:"d.m.Y H:i" }}</div>
|
||||
</td>
|
||||
<td style="padding:0.75rem 1rem; color:var(--color-muted-foreground);">
|
||||
{{ req.summary }}
|
||||
{% if req.note %}<div style="font-size:0.75rem; margin-top:0.25rem;">{{ req.note }}</div>{% endif %}
|
||||
</td>
|
||||
<td style="padding:0.75rem 1rem; min-width:14rem;">
|
||||
<div style="display:flex; flex-direction:column; gap:0.375rem;">
|
||||
<form hx-post="/hr-requests/{{ req.pk }}/approve/" hx-target="#main-content" hx-indicator="#loader">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-secondary" style="width:100%; font-size:0.75rem; color:hsl(152 55% 30%);">{% trans "Утвердить" %}</button>
|
||||
</form>
|
||||
<form hx-post="/hr-requests/{{ req.pk }}/reject/" hx-target="#main-content" hx-indicator="#loader" style="display:flex; gap:0.375rem;">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="decision_note" placeholder="{% trans 'Причина' %}" class="input" style="font-size:0.75rem; padding:0.3rem 0.5rem; flex:1;">
|
||||
<button type="submit" class="btn btn-secondary" style="font-size:0.75rem; color:var(--color-destructive); white-space:nowrap;">{% trans "Отклонить" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,196 @@
|
||||
{% load i18n %}
|
||||
{% if vacancy_message %}
|
||||
<div style="margin-bottom:1rem; padding:0.65rem 0.9rem; border-radius:0.5rem; background:color-mix(in srgb, #16a34a 12%, var(--color-card)); border:1px solid color-mix(in srgb, #16a34a 30%, var(--color-border)); font-size:0.8125rem;">{{ vacancy_message }}</div>
|
||||
{% endif %}
|
||||
{% if vacancy_error %}
|
||||
<div style="margin-bottom:1rem; padding:0.65rem 0.9rem; border-radius:0.5rem; background:color-mix(in srgb, #dc2626 12%, var(--color-card)); border:1px solid color-mix(in srgb, #dc2626 30%, var(--color-border)); font-size:0.8125rem;">{{ vacancy_error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:0.75rem; margin-bottom:1.25rem;">
|
||||
<div class="stat-card">
|
||||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.5rem;">{% trans "Открытых вакансий" %}</div>
|
||||
<div style="font-size:1.5rem; font-weight:500; color:var(--color-foreground);">{{ vacancies_open }}</div>
|
||||
{% if vacancies_urgent %}<div style="font-size:0.75rem; color:var(--color-destructive); margin-top:0.25rem;">{{ vacancies_urgent }} {% trans "срочных" %}</div>{% endif %}
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.5rem;">{% trans "Кандидатов" %}</div>
|
||||
<div style="font-size:1.5rem; font-weight:500; color:var(--color-foreground);">{{ vacancies_candidates }}</div>
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "на открытых" %}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.5rem;">{% trans "Закрыто наймом" %}</div>
|
||||
<div style="font-size:1.5rem; font-weight:500; color:var(--color-foreground);">{{ vacancies_filled_month }}</div>
|
||||
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.25rem;">{% trans "за месяц" %}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.5rem;">{% trans "Всего в списке" %}</div>
|
||||
<div style="font-size:1.5rem; font-weight:500; color:var(--color-foreground);">{{ vacancies|length }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_manage_vacancies %}
|
||||
<div style="display:flex; justify-content:flex-end; margin-bottom:0.75rem;">
|
||||
<button type="button" id="hr-vacancy-form-toggle" class="btn btn-primary" style="font-size:0.8125rem;"
|
||||
onclick="hrToggleVacancyForm()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:0.25rem; vertical-align:-2px;">
|
||||
<line x1="12" x2="12" y1="5" y2="19"/><line x1="5" x2="19" y1="12" y2="12"/>
|
||||
</svg>
|
||||
{% trans "Новая вакансия" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="hr-vacancy-form-wrap" style="{% if vacancy_error %}display:block;{% else %}display:none;{% endif %} margin-bottom:1.25rem;">
|
||||
<form method="post" action="{% url 'vacancy_save' %}"
|
||||
style="background:var(--color-card); border:1px solid var(--color-border); border-radius:0.75rem; overflow:hidden; box-shadow:0 1px 2px rgb(0 0 0/0.05);">
|
||||
{% csrf_token %}
|
||||
<div style="padding:0.75rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; gap:0.75rem;">
|
||||
<span style="font-size:0.8125rem; font-weight:500; color:var(--color-foreground);">{% trans "Новая вакансия" %}</span>
|
||||
<button type="button" class="btn btn-ghost" style="font-size:0.75rem;" onclick="hrHideVacancyForm()">{% trans "Закрыть" %}</button>
|
||||
</div>
|
||||
<div style="padding:1rem 1.25rem;">
|
||||
<div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(11rem,1fr)); gap:0.75rem; margin-bottom:0.75rem;">
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Название" %}</label>
|
||||
<input type="text" name="title" required class="input" style="width:100%;" placeholder="{% trans 'Фармацевт' %}">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Должность" %}</label>
|
||||
<select name="post_id" class="input" style="width:100%;">
|
||||
<option value="">—</option>
|
||||
{% for p in vacancy_posts %}
|
||||
<option value="{{ p.id }}">{{ p.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Приоритет" %}</label>
|
||||
<select name="priority" class="input" style="width:100%;">
|
||||
<option value="normal">{% trans "Обычный" %}</option>
|
||||
<option value="medium">{% trans "Средний" %}</option>
|
||||
<option value="urgent">{% trans "Срочно" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Кандидатов" %}</label>
|
||||
<input type="number" name="candidates_count" min="0" value="0" class="input" style="width:100%;">
|
||||
</div>
|
||||
{% if can_manage_hiring %}
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Тип локации" %}</label>
|
||||
<select name="user_location_type" id="hr-vacancy-loc-type" class="input" style="width:100%;" onchange="hrVacancyLocToggle(this.value)">
|
||||
<option value="pharmacy">{% trans "Аптека" %}</option>
|
||||
<option value="office">{% trans "Офис" %}</option>
|
||||
<option value="warehouse">{% trans "Склад" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="hr-vacancy-pharmacy-field">
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Аптека" %}</label>
|
||||
<select name="pharmacy_id" class="input" style="width:100%;">
|
||||
<option value="">—</option>
|
||||
{% for ph in vacancy_pharmacies %}
|
||||
<option value="{{ ph.id }}">{{ ph }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div id="hr-vacancy-department-field" style="display:none;">
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Отдел" %}</label>
|
||||
<select name="department_id" class="input" style="width:100%;">
|
||||
<option value="">—</option>
|
||||
{% for d in vacancy_departments %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% else %}
|
||||
<input type="hidden" name="user_location_type" value="pharmacy">
|
||||
{% if vacancy_pharmacies %}
|
||||
<input type="hidden" name="pharmacy_id" value="{{ vacancy_pharmacies.0.id }}">
|
||||
<div>
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Аптека" %}</label>
|
||||
<div style="font-size:0.8125rem; padding:0.45rem 0;">{{ vacancy_pharmacies.0 }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="margin-bottom:0.75rem;">
|
||||
<label style="display:block; font-size:0.7rem; color:var(--color-muted-foreground); margin-bottom:0.2rem;">{% trans "Описание" %}</label>
|
||||
<input type="text" name="description" class="input" style="width:100%;">
|
||||
</div>
|
||||
<div style="display:flex; gap:0.5rem; flex-wrap:wrap;">
|
||||
<button type="submit" class="btn btn-primary" style="font-size:0.8125rem;">{% trans "Создать вакансию" %}</button>
|
||||
<button type="button" class="btn btn-secondary" style="font-size:0.8125rem; border:1px solid var(--color-border);" onclick="hrHideVacancyForm()">{% trans "Отмена" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="margin-bottom:1rem; padding:0.75rem 1rem; border-radius:0.625rem; border:1px solid var(--color-border); background:var(--color-muted); font-size:0.8125rem; color:var(--color-muted-foreground);">
|
||||
{% trans "Создавать вакансии могут HR и заведующий. Фармацевт подаёт запрос на подмену во вкладке «Заявки и отпуска»." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 1px 2px rgb(0 0 0/0.05);">
|
||||
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border);">
|
||||
<h3 style="font-weight:600; color:var(--color-card-foreground); font-size:0.9375rem;">{% trans "Вакансии" %}</h3>
|
||||
</div>
|
||||
<div style="overflow-x:auto;">
|
||||
<table style="width:100%; border-collapse:collapse; font-size:0.875rem;">
|
||||
<thead>
|
||||
<tr style="border-bottom:1px solid var(--color-border);">
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Должность" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Отдел / Аптека" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Кандидатов" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Приоритет" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Статус" %}</th>
|
||||
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Открыта с" %}</th>
|
||||
{% if can_manage_vacancies %}<th style="padding:0.75rem 1rem;"></th>{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for v in vacancies %}
|
||||
<tr style="border-bottom:1px solid var(--color-border);" class="tr-hover">
|
||||
<td style="padding:0.85rem 1rem; font-weight:500; color:var(--color-card-foreground);">
|
||||
{{ v.title }}
|
||||
{% if v.post %}<div style="font-size:0.75rem; color:var(--color-muted-foreground); font-weight:400;">{{ v.post.name }}</div>{% endif %}
|
||||
</td>
|
||||
<td style="padding:0.85rem 1rem; color:var(--color-muted-foreground);">{{ v.place_label }}</td>
|
||||
<td style="padding:0.85rem 1rem; color:var(--color-foreground);">{{ v.candidates_count }}</td>
|
||||
<td style="padding:0.85rem 1rem;">
|
||||
{% if v.priority == 'urgent' %}<span class="badge badge-danger">{% trans "Срочно" %}</span>
|
||||
{% elif v.priority == 'medium' %}<span class="badge badge-warning">{% trans "Средний" %}</span>
|
||||
{% else %}<span class="badge badge-success">{% trans "Обычный" %}</span>{% endif %}
|
||||
</td>
|
||||
<td style="padding:0.85rem 1rem;">{{ v.get_status_display }}</td>
|
||||
<td style="padding:0.85rem 1rem; color:var(--color-muted-foreground);">{{ v.opened_at|date:"d.m.Y" }}</td>
|
||||
{% if can_manage_vacancies %}
|
||||
<td style="padding:0.85rem 1rem; text-align:right; white-space:nowrap;">
|
||||
{% if v.status == 'open' %}
|
||||
<form method="post" action="{% url 'vacancy_set_status' v.pk %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="closed">
|
||||
<button type="submit" class="btn btn-ghost" style="font-size:0.75rem;">{% trans "Закрыть" %}</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'vacancy_set_status' v.pk %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="filled">
|
||||
<button type="submit" class="btn btn-ghost" style="font-size:0.75rem;">{% trans "Закрыта наймом" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'vacancy_set_status' v.pk %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="open">
|
||||
<button type="submit" class="btn btn-ghost" style="font-size:0.75rem;">{% trans "Открыть снова" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" style="padding:2rem; text-align:center; color:var(--color-muted-foreground);">{% trans "Открытых вакансий пока нет" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user