Фильтры сохраняются, мастер получил расширенные возможности
All checks were successful
Deploy MES Core / deploy (push) Successful in 10s

This commit is contained in:
2026-04-01 01:04:46 +03:00
parent c2778d9ec8
commit d0289f6aec
14 changed files with 865 additions and 34 deletions

View File

@@ -0,0 +1,158 @@
{% extends 'base.html' %}
{% block content %}
<div class="card shadow border-secondary">
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-3">
<div>
<h3 class="text-accent mb-0"><i class="bi bi-briefcase me-2"></i>Сделки</h3>
<div class="small text-muted">{{ company.name }}</div>
</div>
<form method="get" class="d-flex align-items-center gap-2">
<span class="small text-muted">Статус:</span>
<div class="d-flex flex-wrap gap-1">
<input type="radio" class="btn-check" name="status" id="cust_s_work" value="work" {% if selected_status == 'work' %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-primary btn-sm" for="cust_s_work">В работе</label>
<input type="radio" class="btn-check" name="status" id="cust_s_lead" value="lead" {% if selected_status == 'lead' %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-secondary btn-sm" for="cust_s_lead">Зашла</label>
<input type="radio" class="btn-check" name="status" id="cust_s_done" value="done" {% if selected_status == 'done' %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-success btn-sm" for="cust_s_done">Завершена</label>
</div>
</form>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#dealModal">
<i class="bi bi-plus-lg me-1"></i>Создать сделку
</button>
<a class="btn btn-outline-secondary btn-sm" href="{% url 'customers' %}">
<i class="bi bi-arrow-left me-1"></i>К заказчикам
</a>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead>
<tr class="table-custom-header">
<th>Сделка</th>
<th>Описание</th>
<th style="width: 140px;">Статус</th>
</tr>
</thead>
<tbody>
{% for d in deals %}
<tr class="planning-row" style="cursor:pointer" data-href="{% url 'planning_deal' d.id %}">
<td><span class="text-accent fw-bold">{{ d.number }}</span></td>
<td class="small text-muted">{{ d.description|default:"" }}</td>
<td>
<span class="badge {% if d.status == 'work' %}bg-primary{% elif d.status == 'done' %}bg-success{% else %}bg-secondary{% endif %}">
{{ d.get_status_display }}
</span>
</td>
</tr>
{% empty %}
<tr><td colspan="3" class="text-center p-5 text-muted">Сделок не найдено</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="dealModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-secondary">
<div class="modal-header border-secondary">
<h5 class="modal-title">Сделка</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label small text-muted">№ Сделки</label>
<input type="text" class="form-control border-secondary" id="dealNumber">
</div>
<div class="mb-3">
<label class="form-label small text-muted">Статус</label>
<select class="form-select border-secondary" id="dealStatus">
<option value="lead">Зашла</option>
<option value="work" selected>В работе</option>
<option value="done">Завершена</option>
</select>
</div>
<div class="mb-0">
<label class="form-label small text-muted">Описание</label>
<textarea class="form-control border-secondary" rows="3" id="dealDescription"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-outline-accent" id="dealSaveBtn">Сохранить</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('tr.planning-row[data-href]').forEach(function (row) {
row.addEventListener('click', function () {
const href = row.getAttribute('data-href');
if (href) window.location.href = href;
});
});
const dealModal = document.getElementById('dealModal');
const dealNumber = document.getElementById('dealNumber');
const dealStatus = document.getElementById('dealStatus');
const dealDescription = document.getElementById('dealDescription');
const dealSaveBtn = document.getElementById('dealSaveBtn');
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return '';
}
async function postForm(url, data) {
const csrftoken = getCookie('csrftoken');
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-CSRFToken': csrftoken,
},
body: new URLSearchParams(data).toString(),
});
if (!res.ok) throw new Error('request_failed');
return await res.json();
}
if (dealModal) {
dealModal.addEventListener('show.bs.modal', function () {
if (dealNumber) dealNumber.value = '';
if (dealDescription) dealDescription.value = '';
if (dealStatus) dealStatus.value = 'work';
});
}
if (dealSaveBtn) {
dealSaveBtn.addEventListener('click', async function () {
const payload = {
number: (dealNumber ? dealNumber.value : ''),
status: dealStatus ? dealStatus.value : 'work',
company_id: '{{ company.id }}',
description: (dealDescription ? dealDescription.value : ''),
};
await postForm('{% url "deal_upsert" %}', payload);
window.location.reload();
});
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,115 @@
{% extends 'base.html' %}
{% block content %}
<div class="card shadow border-secondary">
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
<h3 class="text-accent mb-0"><i class="bi bi-building me-2"></i>Заказчики</h3>
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#companyModal">
<i class="bi bi-plus-lg me-1"></i>Добавить заказчика
</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead>
<tr class="table-custom-header">
<th>Заказчик</th>
<th>Примечание</th>
</tr>
</thead>
<tbody>
{% for c in companies %}
<tr class="customer-row" style="cursor:pointer" data-href="{% url 'customer_deals' c.id %}">
<td class="fw-bold">{{ c.name }}</td>
<td class="small text-muted">{{ c.description|default:"" }}</td>
</tr>
{% empty %}
<tr><td colspan="2" class="text-center p-5 text-muted">Заказчиков не найдено</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="companyModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-secondary">
<div class="modal-header border-secondary">
<h5 class="modal-title">Заказчик</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label small text-muted">Название</label>
<input type="text" class="form-control border-secondary" id="companyName">
</div>
<div class="mb-0">
<label class="form-label small text-muted">Примечание</label>
<textarea class="form-control border-secondary" rows="3" id="companyDescription"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-outline-accent" id="companySaveBtn">Сохранить</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('tr.customer-row[data-href]').forEach(function (row) {
row.addEventListener('click', function () {
const href = row.getAttribute('data-href');
if (href) window.location.href = href;
});
});
const companyModal = document.getElementById('companyModal');
const companyName = document.getElementById('companyName');
const companyDescription = document.getElementById('companyDescription');
const companySaveBtn = document.getElementById('companySaveBtn');
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return '';
}
async function postForm(url, data) {
const csrftoken = getCookie('csrftoken');
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-CSRFToken': csrftoken,
},
body: new URLSearchParams(data).toString(),
});
if (!res.ok) throw new Error('request_failed');
return await res.json();
}
if (companyModal) {
companyModal.addEventListener('show.bs.modal', function () {
if (companyName) companyName.value = '';
if (companyDescription) companyDescription.value = '';
});
}
if (companySaveBtn) {
companySaveBtn.addEventListener('click', async function () {
const payload = {
name: (companyName ? companyName.value : ''),
description: (companyDescription ? companyDescription.value : ''),
};
await postForm('{% url "company_upsert" %}', payload);
window.location.reload();
});
}
});
</script>
{% endblock %}

View File

@@ -1,4 +1,5 @@
{% extends 'base.html' %}
{% load l10n %}
{% block content %}
<div class="row justify-content-center">
@@ -72,13 +73,29 @@
</div>
<div class="col-md-4">
<label class="small text-muted">Лом (кг)</label>
<input type="number" step="0.01" min="0" name="scrap_weight" class="form-control border-secondary" value="{{ item.scrap_weight }}" required>
<input type="number" step="0.01" min="0" name="scrap_weight" class="form-control border-secondary" value="{{ item.scrap_weight|default_if_none:'0'|unlocalize }}" required>
</div>
</div>
</div>
{% else %}
<div class="alert alert-success">Статус: {{ item.get_status_display }}. Сделано: {{ item.quantity_fact }} шт.</div>
<input type="hidden" name="quantity_fact" value="{{ item.quantity_fact }}">
{% if user_role == 'master' %}
<div class="row g-3 mt-3 text-start">
<div class="col-md-4">
<label class="small text-muted">Взятый материал</label>
<input type="text" name="material_taken" class="form-control border-secondary" value="{{ item.material_taken }}" placeholder="Напр: 3 трубы по 12м">
</div>
<div class="col-md-4">
<label class="small text-muted">Остаток ДО</label>
<input type="text" name="usable_waste" class="form-control border-secondary" value="{{ item.usable_waste }}" placeholder="Напр: 0.8м / 12кг">
</div>
<div class="col-md-4">
<label class="small text-muted">Лом (кг)</label>
<input type="number" step="0.01" min="0" name="scrap_weight" class="form-control border-secondary" value="{{ item.scrap_weight|default_if_none:'0'|unlocalize }}">
</div>
</div>
{% endif %}
{% endif %}
{% endif %}
@@ -124,7 +141,7 @@
</div>
<div class="col-md-4">
<label class="small text-muted">Лом (кг)</label>
<input type="number" step="0.01" name="scrap_weight" class="form-control border-secondary" value="{{ item.scrap_weight }}">
<input type="number" step="0.01" name="scrap_weight" class="form-control border-secondary" value="{{ item.scrap_weight|default_if_none:'0'|unlocalize }}">
</div>
</div>

View File

@@ -73,14 +73,63 @@
</form>
<script>
document.addEventListener('DOMContentLoaded', function(){
const form = document.getElementById('filter-form');
const s = document.querySelector('input[name="start_date"]');
const e = document.querySelector('input[name="end_date"]');
const now = new Date();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const today = `${now.getFullYear()}-${mm}-${dd}`;
if (s && !s.value) s.value = today;
const weekAgoDate = new Date(now);
weekAgoDate.setDate(weekAgoDate.getDate() - 7);
const mm2 = String(weekAgoDate.getMonth() + 1).padStart(2, '0');
const dd2 = String(weekAgoDate.getDate()).padStart(2, '0');
const weekAgo = `${weekAgoDate.getFullYear()}-${mm2}-${dd2}`;
if (s && !s.value) s.value = weekAgo;
if (e && !e.value) e.value = today;
function saveFilters(){
if (!form) return;
const data = {
statuses: Array.from(form.querySelectorAll('input[name="statuses"]:checked')).map(i=>i.value),
m_ids: Array.from(form.querySelectorAll('input[name="m_ids"]:checked')).map(i=>i.value),
start_date: s ? s.value : '',
is_synced: (form.querySelector('select[name="is_synced"]')||{}).value || ''
};
try { localStorage.setItem('registry_filters', JSON.stringify(data)); } catch(_){}
}
function restoreFilters(){
if (!form) return false;
const qs = new URLSearchParams(window.location.search);
if (qs.get('filtered') === '1') return false;
let raw = null; try { raw = localStorage.getItem('registry_filters'); } catch(_){}
if (!raw) return false;
let data = null; try { data = JSON.parse(raw); } catch(_){}
if (!data) return false;
if (Array.isArray(data.statuses)){
form.querySelectorAll('input[name="statuses"]').forEach(i=>{ i.checked = data.statuses.includes(i.value); });
}
if (Array.isArray(data.m_ids)){
form.querySelectorAll('input[name="m_ids"]').forEach(i=>{ i.checked = data.m_ids.includes(i.value); });
}
if (s) s.value = data.start_date || weekAgo;
if (e) e.value = today;
const sel = form.querySelector('select[name="is_synced"]');
if (sel && data.is_synced !== undefined) sel.value = data.is_synced;
const filtered = form.querySelector('input[name="filtered"]');
if (filtered) filtered.value = '1';
form.submit();
return true;
}
if (form){
form.addEventListener('change', saveFilters, true);
restoreFilters();
}
});
</script>
</div>

View File

@@ -4,7 +4,7 @@
<div class="card shadow border-secondary">
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-3">
<h3 class="text-accent mb-0"><i class="bi bi-kanban me-2"></i>Планирование</h3>
<h3 class="text-accent mb-0"><i class="bi bi-kanban me-2"></i>Сделки</h3>
<form method="get" class="d-flex align-items-center gap-2">
<span class="small text-muted">Сделки:</span>
<div class="d-flex flex-wrap gap-1">
@@ -134,6 +134,21 @@ document.addEventListener('DOMContentLoaded', function () {
});
});
const statusRadios = document.querySelectorAll('input[name="status"]');
const qs = new URLSearchParams(window.location.search);
if (!qs.get('status')){
try{
const saved = localStorage.getItem('planning_status');
if (saved){
const r = Array.from(statusRadios).find(x=>x.value===saved);
if (r && !r.checked){ r.checked = true; r.form.submit(); }
}
}catch(_){ }
}
statusRadios.forEach(r=> r.addEventListener('change', ()=>{
try{ localStorage.setItem('planning_status', r.value); }catch(_){ }
}));
const dealModal = document.getElementById('dealModal');
const dealId = document.getElementById('dealId');
const dealNumber = document.getElementById('dealNumber');

View File

@@ -20,7 +20,7 @@
<a class="btn btn-outline-secondary btn-sm" href="{% url 'planning' %}">
<i class="bi bi-arrow-left me-1"></i>Назад
</a>
<a class="btn btn-outline-accent btn-sm" href="{% url 'task_add' %}?deal={{ deal.id }}">
<a class="btn btn-outline-accent btn-sm" href="{% url 'task_add' %}?deal={{ deal.id }}&next={% url 'planning_deal' deal.id %}">
<i class="bi bi-plus-lg me-1"></i>Добавить деталь
</a>
</div>
@@ -43,7 +43,7 @@
</thead>
<tbody>
{% for t in tasks %}
<tr>
<tr class="task-row" style="cursor:pointer" data-href="{% url 'task_items' t.id %}">
<td class="fw-bold">{{ t.drawing_name|default:"Б/ч" }}</td>
<td class="small text-muted">{{ t.material.full_name|default:t.material.name }}</td>
<td class="small">{{ t.size_value }}</td>
@@ -88,12 +88,13 @@
<div class="small text-muted mb-2" id="modalTaskTitle"></div>
<div class="mb-3">
<label class="form-label small text-muted">Станок</label>
<select class="form-select border-secondary" name="machine_id" required>
<label class="form-label small text-muted d-block">Станок</label>
<div class="d-flex flex-wrap gap-1" id="machineToggleGroup">
{% for m in machines %}
<option value="{{ m.id }}">{{ m.name }}</option>
<input type="radio" class="btn-check" name="machine_id" id="m_{{ m.id }}" value="{{ m.id }}" required>
<label class="btn btn-outline-accent btn-sm" for="m_{{ m.id }}">{{ m.name }}</label>
{% endfor %}
</select>
</div>
</div>
<div class="mb-2">
@@ -113,10 +114,18 @@
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('tr.task-row[data-href]').forEach(function (row) {
row.addEventListener('click', function (e) {
if (e.target && e.target.closest('button')) return;
const href = row.getAttribute('data-href');
if (href) window.location.href = href;
});
});
const modal = document.getElementById('addToPlanModal');
if (!modal) return;
modal.addEventListener('show.bs.modal', function (event) {
modal.addEventListener('shown.bs.modal', function (event) {
const btn = event.relatedTarget;
const taskId = btn.getAttribute('data-task-id');
const name = btn.getAttribute('data-task-name');
@@ -128,8 +137,42 @@ document.addEventListener('DOMContentLoaded', function () {
const qty = document.getElementById('modalQty');
qty.value = '';
if (rem && !isNaN(parseInt(rem, 10))) qty.max = Math.max(1, parseInt(rem, 10));
else qty.removeAttribute('max');
let remInt = null;
if (rem && !isNaN(parseInt(rem, 10))) {
remInt = Math.max(1, parseInt(rem, 10));
qty.max = remInt;
qty.value = String(remInt);
} else {
qty.removeAttribute('max');
}
qty.focus({ preventScroll: true });
qty.select();
qty.onkeydown = function (e) {
if (e.key === 'Enter') {
e.preventDefault();
const form = document.querySelector('#addToPlanModal form');
if (form) form.requestSubmit();
}
};
const radios = Array.from(document.querySelectorAll('input[name="machine_id"]'));
const savedMachine = (() => { try { return localStorage.getItem('planning_machine_id'); } catch (_) { return null; } })();
let selected = null;
if (savedMachine) {
selected = radios.find(r => r.value === savedMachine);
}
if (!selected && radios.length) selected = radios[0];
if (selected) selected.checked = true;
radios.forEach(r => {
r.onchange = function () {
try { localStorage.setItem('planning_machine_id', r.value); } catch (_) {}
};
});
});
});
</script>

View File

@@ -0,0 +1,20 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Сохранено</title>
</head>
<body>
<script>
(function () {
try {
if (window.opener && typeof window.opener.shiftflowReceivePopup === 'function') {
window.opener.shiftflowReceivePopup('{{ target|escapejs }}', '{{ value }}', '{{ label|escapejs }}');
}
} catch (e) {}
window.close();
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,46 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ view.model._meta.verbose_name }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="p-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">{{ view.model._meta.verbose_name }}</h5>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.close()">Закрыть</button>
</div>
<form method="post">
{% csrf_token %}
<input type="hidden" name="target" value="{{ target }}">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for e in form.non_field_errors %}<div>{{ e }}</div>{% endfor %}
</div>
{% endif %}
{% for field in form %}
<div class="mb-3">
<label class="form-label small text-muted">{{ field.label }}</label>
{% if field.field.widget.input_type == "checkbox" %}
<div class="form-check">
{{ field }}
</div>
{% else %}
{{ field }}
{% endif %}
{% if field.help_text %}<div class="form-text">{{ field.help_text }}</div>{% endif %}
{% for e in field.errors %}<div class="text-danger small">{{ e }}</div>{% endfor %}
</div>
{% endfor %}
<div class="d-flex gap-2 justify-content-end">
<button type="button" class="btn btn-outline-secondary" onclick="window.close()">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button>
</div>
</form>
</body>
</html>

View File

@@ -6,13 +6,20 @@
<div class="card shadow border-secondary">
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
<h3 class="text-accent mb-0"><i class="bi bi-plus-circle me-2"></i>Новое задание</h3>
<a class="btn btn-outline-secondary btn-sm" href="{% url 'planning' %}">
<i class="bi bi-arrow-left me-1"></i>Назад
</a>
{% if request.GET.next %}
<a class="btn btn-outline-secondary btn-sm" href="{{ request.GET.next }}">
<i class="bi bi-arrow-left me-1"></i>Назад
</a>
{% else %}
<a class="btn btn-outline-secondary btn-sm" href="{% url 'planning' %}">
<i class="bi bi-arrow-left me-1"></i>Назад
</a>
{% endif %}
</div>
<form method="post" enctype="multipart/form-data" class="card-body p-4">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.GET.next }}">
{% if form.non_field_errors %}
<div class="alert alert-danger">

View File

@@ -0,0 +1,92 @@
{% extends 'base.html' %}
{% block content %}
<div class="card shadow border-secondary mb-3">
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
<div>
<h3 class="text-accent mb-1">
<i class="bi bi-list-task me-2"></i>{{ task.drawing_name|default:"Б/ч" }}
</h3>
<div class="small text-muted">
Сделка {{ task.deal.number }}{% if task.deal.company %} · {{ task.deal.company.name }}{% endif %}
</div>
</div>
<a class="btn btn-outline-secondary btn-sm" href="{% url 'planning_deal' task.deal.id %}">
<i class="bi bi-arrow-left me-1"></i>Назад к сделке
</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead>
<tr class="table-custom-header">
<th>Дата</th>
<th>Сделка</th>
<th>Станок</th>
<th>Наименование</th>
<th>Габариты</th>
<th>План / Факт</th>
<th>Материал</th>
<th class="text-center">Файлы</th>
<th class="text-center">1С</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
{% for it in items %}
<tr class="clickable-row" data-href="{% url 'item_detail' it.pk %}">
<td class="small">{{ it.date|date:"d.m.y" }}</td>
<td><span class="text-accent fw-bold">{{ it.task.deal.number|default:"-" }}</span></td>
<td><span class="badge bg-dark border border-secondary">{{ it.machine.name }}</span></td>
<td class="fw-bold">{{ it.task.drawing_name|default:"Б/ч" }}</td>
<td class="small">{{ it.task.size_value|default:"-" }}</td>
<td>
<span class="text-info fw-bold">{{ it.quantity_plan }}</span> /
<span class="text-success">{{ it.quantity_fact }}</span>
</td>
<td class="small text-muted">{{ it.task.material.full_name|default:it.task.material.name|default:"-" }}</td>
<td class="text-center">
{% if it.task.drawing_file %}
<a href="{{ it.task.drawing_file.url }}" target="_blank" class="btn btn-sm btn-outline-info p-1 stop-prop" title="DXF/STEP">
<i class="bi bi-file-earmark-code"></i>
</a>
{% endif %}
{% if it.task.extra_drawing %}
<a href="{{ it.task.extra_drawing.url }}" target="_blank" class="btn btn-sm btn-outline-danger p-1 stop-prop" title="Чертеж PDF">
<i class="bi bi-file-pdf"></i>
</a>
{% endif %}
</td>
<td class="text-center">
{% if it.is_synced_1c %}
<i class="bi bi-check-circle-fill text-success" title="Учтено"></i>
{% else %}
<i class="bi bi-clock-history text-muted" title="Ожидает"></i>
{% endif %}
</td>
<td>
<span class="badge {% if it.status == 'work' %}bg-primary{% elif it.status == 'done' %}bg-success{% elif it.status == 'partial' %}bg-success-subtle text-success-emphasis border border-success-subtle{% else %}bg-secondary{% endif %}">{{ it.get_status_display }}</span>
</td>
</tr>
{% empty %}
<tr><td colspan="10" class="text-center p-5 text-muted">Пунктов сменки не найдено</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const rows = document.querySelectorAll(".clickable-row");
rows.forEach(row => {
row.addEventListener("click", function(e) {
if (e.target.closest('.stop-prop')) return;
window.location.href = this.dataset.href;
});
});
});
</script>
{% endblock %}