hiring: вакансии и найм, HR-портал

Модель вакансий, флоу рекрутинга и панели HR-портала (заявки/вакансии).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 18:03:32 +03:00
parent 3359a8db13
commit ac0418083b
12 changed files with 1317 additions and 286 deletions
+217
View File
@@ -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>
+196
View File
@@ -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>