ac0418083b
Модель вакансий, флоу рекрутинга и панели HR-портала (заявки/вакансии). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
218 lines
17 KiB
HTML
218 lines
17 KiB
HTML
{% 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>
|