This commit is contained in:
Flatlogic Bot 2026-01-27 21:42:27 +00:00
parent 9e85e053cc
commit e5089ee366
21 changed files with 1167 additions and 821 deletions

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-01-27 20:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_fleetunit_contract_number_and_more'),
]
operations = [
migrations.CreateModel(
name='MaintenancePart',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('part_name', models.CharField(max_length=255, verbose_name='Наименование (фильтр, масло и т.д.)')),
('article_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='Артикул')),
('quantity', models.CharField(max_length=50, verbose_name='Количество')),
('maintenance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_parts', to='core.maintenance', verbose_name='ТО')),
],
options={
'verbose_name': 'Запчасть ТО',
'verbose_name_plural': 'Запчасти ТО',
},
),
]

View File

@ -216,4 +216,17 @@ class Document(models.Model):
verbose_name_plural = "Документы"
def __str__(self):
return f"{self.get_doc_type_display()} - {self.uploaded_at}"
return f"{self.get_doc_type_display()} - {self.uploaded_at}"
class MaintenancePart(models.Model):
maintenance = models.ForeignKey(Maintenance, on_delete=models.CASCADE, related_name='used_parts', verbose_name='ТО')
part_name = models.CharField(max_length=255, verbose_name='Наименование (фильтр, масло и т.д.)')
article_number = models.CharField(max_length=100, blank=True, null=True, verbose_name='Артикул')
quantity = models.CharField(max_length=50, verbose_name='Количество')
class Meta:
verbose_name = 'Запчасть ТО'
verbose_name_plural = 'Запчасти ТО'
def __str__(self):
return f'{self.part_name} ({self.quantity})'

View File

@ -6,11 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Fleet Manager{% endblock %}</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet">
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
@ -21,48 +16,45 @@
{% block extra_css %}{% endblock %}
</head>
<body class="bg-light">
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-slate-900 sticky-top shadow-sm">
<nav class="navbar navbar-expand-lg sticky-top navbar-apple">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="{% url 'index' %}">
<i class="bi bi-truck-flatbed me-2 text-blue-500"></i>
<span class="fw-bold text-uppercase tracking-wider">Fleet<span class="text-blue-500">Manager</span></span>
<i class="bi bi-apple me-2 text-primary"></i>
<span class="fw-bold">Fleet Manager</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'index' %}active{% endif %}" href="{% url 'index' %}">Дашборд</a>
<a class="nav-link {% if request.resolver_match.url_name == 'index' %}fw-bold{% endif %}" href="{% url 'index' %}">Дашборд</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'fleet' in request.path %}active{% endif %}" href="{% url 'fleet_list' %}">Техника</a>
<a class="nav-link {% if 'fleet' in request.path %}fw-bold{% endif %}" href="{% url 'fleet_list' %}">Техника</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'maintenance' in request.path %}active{% endif %}" href="{% url 'maintenance_list' %}">ТО</a>
<a class="nav-link {% if 'maintenance' in request.path %}fw-bold{% endif %}" href="{% url 'maintenance_list' %}">ТО</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'part-request' in request.path %}active{% endif %}" href="{% url 'part_request_list' %}">Заявки</a>
<a class="nav-link {% if 'part-request' in request.path %}fw-bold{% endif %}" href="{% url 'part_request_list' %}">Заявки</a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'supply' in request.path or 'supplier' in request.path %}active{% endif %}" href="{% url 'supply_list' %}">Снабжение</a>
<a class="nav-link {% if 'supply' in request.path or 'supplier' in request.path %}fw-bold{% endif %}" href="{% url 'supply_list' %}">Снабжение</a>
</li>
</ul>
<div class="d-flex align-items-center">
<span class="text-white-50 me-3 d-none d-md-inline small">
{% if user.is_authenticated %}{{ user.get_full_name|default:user.username }}{% else %}Гость{% endif %}
</span>
<div class="dropdown">
<a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown">
<img src="https://ui-avatars.com/api/?name={{ user.username }}&background=3b82f6&color=fff" alt="user" width="32" height="32" class="rounded-circle">
<a href="#" class="d-block link-dark text-decoration-none dropdown-toggle no-caret" id="dropdownUser1" data-bs-toggle="dropdown">
<img src="https://ui-avatars.com/api/?name={{ user.username }}&background=0071e3&color=fff" alt="user" width="28" height="28" class="rounded-circle">
</a>
<ul class="dropdown-menu dropdown-menu-end text-small shadow border-0 mt-2" aria-labelledby="dropdownUser1">
<li><a class="dropdown-item" href="/admin/">Админ-панель</a></li>
<li><a class="dropdown-item" href="#">Профиль</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/admin/logout/">Выйти</a></li>
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 mt-3 rounded-4" aria-labelledby="dropdownUser1">
<li class="px-3 py-2 small text-secondary-apple">Вошли как: <strong>{{ user.username }}</strong></li>
<li><hr class="dropdown-divider opacity-50"></li>
<li><a class="dropdown-item py-2" href="/admin/"><i class="bi bi-gear me-2"></i>Админ-панель</a></li>
<li><a class="dropdown-item py-2" href="/admin/logout/"><i class="bi bi-box-arrow-right me-2"></i>Выйти</a></li>
</ul>
</div>
</div>
@ -70,18 +62,23 @@
</div>
</nav>
<main class="py-4">
<main class="container py-5">
{% block content %}{% endblock %}
</main>
<footer class="py-4 bg-white mt-auto border-top">
<footer class="py-5 mt-auto">
<div class="container text-center">
<p class="text-muted small mb-0">&copy; {% now "Y" %} Fleet Manager. Разработано для управления парком техники.</p>
<p class="text-secondary-apple small mb-0">&copy; {% now "Y" %} Fleet Manager. Совершенство в деталях.</p>
</div>
</footer>
<!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.no-caret::after { display: none !important; }
.dropdown-item:hover { background-color: #f5f5f7; border-radius: 8px; }
.dropdown-menu { min-width: 200px; padding: 8px; }
</style>
{% block extra_js %}{% endblock %}
</body>
</html>

View File

@ -2,56 +2,61 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="fw-bold mb-0">Журнал поломок</h3>
<a href="{% url 'breakdown_add' %}" class="btn btn-danger rounded-pill px-4">
<i class="bi bi-exclamation-triangle me-2"></i>Сообщить о поломке
</a>
<div class="container">
<div class="row mb-5">
<div class="col-md-8">
<h1 class="display-6 fw-bold mb-2">Журнал поломок</h1>
<p class="text-secondary-apple">Учет неисправностей и контроль их устранения.</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'breakdown_add' %}" class="btn btn-danger">
<i class="bi bi-exclamation-triangle me-2"></i>Сообщить о поломке
</a>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card mb-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<thead>
<tr>
<th class="border-0 px-4">Техника</th>
<th class="border-0">Дата</th>
<th class="border-0">Узел / Система</th>
<th class="border-0">Статус</th>
<th class="border-0 text-end px-4">Действия</th>
<th class="ps-4">Техника</th>
<th>Дата</th>
<th>Узел / Система</th>
<th>Статус</th>
<th class="pe-4 text-end">Действия</th>
</tr>
</thead>
<tbody>
{% for b in breakdowns %}
<tr>
<td class="px-4">
<div class="fw-bold"><a href="{{ b.fleet_unit.get_absolute_url }}">{{ b.fleet_unit.name }}</a></div>
<div class="small text-muted">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div>
<td class="ps-4">
<div class="fw-bold"><a href="{{ b.fleet_unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ b.fleet_unit.name }}</a></div>
<div class="small text-secondary-apple">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div>
</td>
<td>{{ b.date }}</td>
<td>{{ b.date|date:"d.m.Y" }}</td>
<td>{{ b.system_node }}</td>
<td>
{% if b.status == 'reported' %}
<span class="badge bg-danger rounded-pill">Заявлено</span>
<span class="badge bg-danger">Заявлено</span>
{% elif b.status == 'repaired' %}
<span class="badge bg-success rounded-pill">Отремонтировано</span>
<span class="badge bg-success">Отремонтировано</span>
{% elif b.status == 'need_part' %}
<span class="badge bg-warning text-dark rounded-pill">Нужна деталь</span>
<span class="badge bg-warning text-dark">Нужна деталь</span>
{% else %}
<span class="badge bg-secondary rounded-pill">{{ b.status }}</span>
<span class="badge bg-secondary">{{ b.get_status_display }}</span>
{% endif %}
</td>
<td class="text-end px-4">
<div class="btn-group">
<a href="{{ b.fleet_unit.get_absolute_url }}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">К технике</a>
<td class="pe-4 text-end">
<div class="d-flex justify-content-end align-items-center">
<a href="{{ b.fleet_unit.get_absolute_url }}" class="btn btn-sm btn-link text-decoration-none me-2">К технике</a>
{% if b.status == 'need_part' %}
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-warning rounded-pill px-3 me-2">Заказать запчасть</a>
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-warning py-1 px-3 small me-2">Заказать запчасть</a>
{% endif %}
{% if user.is_staff %}
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger rounded-pill px-2" title="Удалить"><i class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</div>
@ -59,7 +64,7 @@
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-muted">Нет зафиксированных поломок</td>
<td colspan="5" class="text-center py-5 text-secondary-apple">Нет зафиксированных поломок</td>
</tr>
{% endfor %}
</tbody>
@ -67,4 +72,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -2,47 +2,56 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="row">
<div class="container">
<div class="row mb-5">
<div class="col-md-8">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-2">
<li class="breadcrumb-item"><a href="{% url 'fleet_list' %}" class="text-decoration-none text-secondary-apple">Техника</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ unit.name }}</li>
</ol>
</nav>
<h1 class="display-6 fw-bold mb-0">{{ unit.name }}</h1>
<p class="text-secondary-apple">{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<span class="badge bg-{{ unit.get_status_color }} text-white px-3 py-2 fs-6">
{{ unit.get_status_display }}
</span>
</div>
</div>
<div class="row g-4">
<!-- Sidebar: Info & QR -->
<div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card mb-4 overflow-hidden">
{% if unit.photo %}
<img src="{{ unit.photo.url }}" class="card-img-top" alt="{{ unit.name }}" style="height: 250px; object-fit: cover;">
<img src="{{ unit.photo.url }}" class="w-100" alt="{{ unit.name }}" style="height: 300px; object-fit: cover;">
{% else %}
<div class="bg-light text-center py-5">
<i class="bi bi-truck text-muted display-1"></i>
<i class="bi bi-truck text-secondary-apple display-1"></i>
</div>
{% endif %}
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h4 class="fw-bold mb-0">{{ unit.name }}</h4>
<span class="text-muted small">{{ unit.model_name }}</span>
</div>
<span class="badge bg-{{ unit.get_status_color }} rounded-pill px-3 py-2">
{{ unit.get_status_display }}
</span>
</div>
<div class="card-body p-4">
<h5 class="fw-bold mb-4">Характеристики</h5>
<div class="row g-3 mb-4">
<div class="col-6">
<p class="text-muted small mb-0">Госномер</p>
<p class="text-secondary-apple small mb-0">Госномер</p>
<p class="fw-bold mb-0">{{ unit.plate_number|default:"-" }}</p>
</div>
<div class="col-6">
<p class="text-muted small mb-0">Год выпуска</p>
<p class="text-secondary-apple small mb-0">Год выпуска</p>
<p class="fw-bold mb-0">{{ unit.year }}</p>
</div>
<div class="col-12">
<p class="text-muted small mb-0">VIN / Серийный номер</p>
<div class="col-12 border-top pt-3">
<p class="text-secondary-apple small mb-0">VIN / Серийный номер</p>
<p class="fw-bold mb-0">{{ unit.vin }}</p>
</div>
</div>
{% if unit.insurance_company %}
<div class="bg-light p-3 rounded mb-4">
<h6 class="fw-bold small mb-2"><i class="bi bi-shield-check me-2"></i>Страховка</h6>
<div class="p-3 rounded-4 bg-light mb-4">
<h6 class="fw-bold small mb-2 text-primary"><i class="bi bi-shield-check me-2"></i>Страховка</h6>
<p class="small mb-1"><strong>Компания:</strong> {{ unit.insurance_company }}</p>
<p class="small mb-1"><strong>Полис:</strong> {{ unit.insurance_policy_number|default:"-" }}</p>
<p class="small mb-0"><strong>Срок:</strong> {{ unit.insurance_start_date|date:"d.m.Y" }} — {{ unit.insurance_end_date|date:"d.m.Y" }}</p>
@ -50,89 +59,89 @@
{% endif %}
<div class="d-grid gap-2">
<a href="{% url 'fleet_edit' unit.pk %}" class="btn btn-outline-primary rounded-pill">Редактировать</a>
<a href="{% url 'fleet_edit' unit.pk %}" class="btn btn-outline-primary">Редактировать</a>
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-danger">Заявить о поломке</a>
{% if user.is_staff %}
<form action="{% url 'fleet_delete' unit.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Вы уверены, что хотите удалить эту единицу техники? Все связанные данные будут удалены.')">
<form action="{% url 'fleet_delete' unit.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Вы уверены, что хотите удалить эту единицу техники?')">
{% csrf_token %}
<button type="submit" class="btn btn-outline-danger rounded-pill w-100 mb-2">Удалить технику</button>
<button type="submit" class="btn btn-link text-danger w-100 small text-decoration-none mt-2">Удалить технику</button>
</form>
{% endif %}
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-danger rounded-pill">Заявить о поломке</a>
</div>
</div>
</div>
<div class="card shadow-sm border-0 text-center p-4">
<h6 class="fw-bold mb-3">QR-код техники</h6>
<div class="card p-4 text-center">
<h6 class="fw-bold mb-3">QR-код доступа</h6>
{% if unit.qr_code %}
<img src="{{ unit.qr_code.url }}" class="img-fluid mx-auto mb-3" style="max-width: 150px;" alt="QR Code">
<img src="{{ unit.qr_code.url }}" class="img-fluid mx-auto mb-3" style="max-width: 140px;" alt="QR Code">
{% else %}
<div class="alert alert-light small">QR-код генерируется...</div>
<div class="text-secondary-apple small py-3">Генерация...</div>
{% endif %}
<p class="text-muted small mb-0">Наклейте этот код на технику для быстрого доступа механика.</p>
<button onclick="window.print()" class="btn btn-sm btn-link mt-2">Печать кода</button>
<p class="text-secondary-apple small mb-3">Для быстрого перехода механика к истории и заявкам.</p>
<button onclick="window.print()" class="btn btn-sm btn-link text-decoration-none">Печать кода <i class="bi bi-printer ms-1"></i></button>
</div>
</div>
<!-- Main Content: Tabs -->
<div class="col-lg-8">
<div class="card shadow-sm border-0">
<div class="card-header bg-white border-0 pt-3">
<ul class="nav nav-pills" id="fleetTab" role="tablist">
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs border-0" id="fleetTab" role="tablist">
<li class="nav-item">
<button class="nav-link active rounded-pill px-4" id="history-tab" data-bs-toggle="tab" data-bs-target="#history" type="button">История ТО</button>
<button class="nav-link active fw-bold border-0" id="history-tab" data-bs-toggle="tab" data-bs-target="#history" type="button">ТО</button>
</li>
<li class="nav-item">
<button class="nav-link rounded-pill px-4" id="breakdowns-tab" data-bs-toggle="tab" data-bs-target="#breakdowns" type="button">Поломки</button>
<button class="nav-link fw-bold border-0" id="breakdowns-tab" data-bs-toggle="tab" data-bs-target="#breakdowns" type="button">Поломки</button>
</li>
<li class="nav-item">
<button class="nav-link rounded-pill px-4" id="parts-tab" data-bs-toggle="tab" data-bs-target="#parts" type="button">Заявки на запчасти</button>
<button class="nav-link fw-bold border-0" id="parts-tab" data-bs-toggle="tab" data-bs-target="#parts" type="button">Заявки</button>
</li>
<li class="nav-item">
<button class="nav-link rounded-pill px-4" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply" type="button">Снабжение</button>
<button class="nav-link fw-bold border-0" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply" type="button">Снабжение</button>
</li>
</ul>
</div>
<div class="card-body">
<div class="card-body p-4">
<div class="tab-content" id="fleetTabContent">
<!-- Maintenance Tab -->
<div class="tab-pane fade show active" id="history">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-bold mb-0">Плановое и выполненное ТО</h6>
<a href="{% url 'maintenance_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary rounded-pill">+ Запланировать</a>
<div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">История и планирование</h6>
<a href="{% url 'maintenance_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary">+ Запланировать</a>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle">
<table class="table align-middle">
<thead>
<tr>
<th>Тип</th>
<th>Дата</th>
<th>Статус</th>
<th>Исполнитель</th>
<th></th>
<th>Механик</th>
<th class="text-end">Действие</th>
</tr>
</thead>
<tbody>
{% for m in maintenances %}
<tr>
<td>{{ m.m_type }}</td>
<td class="fw-bold">{{ m.m_type }}</td>
<td>{{ m.planned_date }}</td>
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill small">{{ m.get_status_display }}</span></td>
<td>{{ m.mechanic|default:"-" }}</td>
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">{{ m.get_status_display }}</span></td>
<td class="small">{{ m.mechanic|default:"-" }}</td>
<td class="text-end">
<div class="btn-group">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-primary p-0 me-2">Открыть</a>
<div class="d-flex justify-content-end align-items-center">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none">Открыть</a>
{% if user.is_staff %}
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')">
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline ms-2">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="5" class="text-center py-4 text-muted">Нет записей ТО</td></tr>
<tr><td colspan="5" class="text-center py-5 text-secondary-apple">Записей не найдено</td></tr>
{% endfor %}
</tbody>
</table>
@ -141,72 +150,72 @@
<!-- Breakdowns Tab -->
<div class="tab-pane fade" id="breakdowns">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">Журнал поломок</h6>
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-danger rounded-pill">+ Новая поломка</a>
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-danger">+ Заявить</a>
</div>
<div class="list-group list-group-flush">
<div class="list-group list-group-flush border-top">
{% for b in breakdowns %}
<div class="list-group-item px-0 py-3">
<div class="d-flex justify-content-between mb-1">
<div class="d-flex justify-content-between mb-2">
<h6 class="mb-0 fw-bold text-danger">{{ b.system_node }}</h6>
<div>
<span class="badge bg-light text-dark border small me-2">{{ b.get_status_display }}</span>
<div class="d-flex align-items-center">
<span class="badge bg-light text-dark border-0 shadow-sm me-3">{{ b.get_status_display }}</span>
{% if user.is_staff %}
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')">
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</div>
</div>
<p class="small mb-1 text-muted">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p>
<div class="d-flex align-items-center mt-2">
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-primary rounded-pill py-0 small me-2">+ Заказать запчасть</a>
{% if b.photo %}<span class="badge bg-light text-muted border small"><i class="bi bi-image me-1"></i>Есть фото</span>{% endif %}
<p class="small text-secondary-apple mb-3">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p>
<div class="d-flex align-items-center">
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-primary py-1 px-3 small me-2">Заказать деталь</a>
{% if b.photo %}<span class="text-secondary-apple small"><i class="bi bi-image me-1"></i>Фото приложено</span>{% endif %}
</div>
</div>
{% empty %}
<div class="text-center py-4 text-muted">Поломок не зафиксировано</div>
<div class="text-center py-5 text-secondary-apple">Журнал пуст</div>
{% endfor %}
</div>
</div>
<!-- Parts Tab -->
<div class="tab-pane fade" id="parts">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">Заявки на запчасти</h6>
<a href="{% url 'part_request_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary rounded-pill">+ Создать заявку</a>
<a href="{% url 'part_request_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary">+ Новая заявка</a>
</div>
<div class="table-responsive">
<table class="table table-sm align-middle">
<table class="table align-middle">
<thead>
<tr>
<th>Деталь</th>
<th>Кол-во</th>
<th>Статус</th>
<th>Дата</th>
<th></th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
{% for r in part_requests %}
<tr>
<td>{{ r.part_name }}</td>
<td class="fw-bold">{{ r.part_name }}</td>
<td>{{ r.quantity }}</td>
<td><span class="badge bg-light text-dark border rounded-pill small">{{ r.get_status_display }}</span></td>
<td><span class="badge bg-light text-dark border-0 shadow-sm">{{ r.get_status_display }}</span></td>
<td class="small">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="text-end">
{% if user.is_staff %}
<form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
<form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="5" class="text-center py-4 text-muted">Нет заявок</td></tr>
<tr><td colspan="5" class="text-center py-5 text-secondary-apple">Активных заявок нет</td></tr>
{% endfor %}
</tbody>
</table>
@ -215,85 +224,63 @@
<!-- Supply Tab -->
<div class="tab-pane fade" id="supply">
<h6 class="fw-bold mb-3">Данные по снабжению и поставщику</h6>
<div class="row g-4">
<div class="col-md-6">
<div class="mb-3">
<p class="text-muted small mb-1">Номер договора (техника)</p>
<p class="fw-bold mb-0">{{ unit.contract_number|default:"-" }}</p>
<div class="col-md-7">
<div class="mb-4">
<h6 class="fw-bold mb-3">Договорные данные</h6>
<div class="p-3 bg-light rounded-4">
<p class="text-secondary-apple small mb-1">Номер договора</p>
<p class="fw-bold mb-0 h5">{{ unit.contract_number|default:"Не указан" }}</p>
</div>
</div>
{% if unit.supplier %}
<p class="text-muted small mb-1">Поставщик / Контрагент</p>
<p class="fw-bold mb-1">{{ unit.supplier.name }}</p>
<p class="text-muted small mb-1">Представитель</p>
<p class="small mb-1">{{ unit.supplier.representative_name|default:"-" }}</p>
<p class="small mb-1"><i class="bi bi-telephone me-2"></i>{{ unit.supplier.phone|default:"-" }}</p>
<p class="small"><i class="bi bi-envelope me-2"></i>{{ unit.supplier.email|default:"-" }}</p>
{% else %}
<p class="text-muted small">Поставщик не привязан</p>
<p class="small text-muted">Вы можете выбрать поставщика в режиме редактирования техники.</p>
{% endif %}
<div>
<h6 class="fw-bold mb-3">Поставщик</h6>
{% if unit.supplier %}
<div class="card p-3 border-0 shadow-sm bg-white">
<p class="fw-bold mb-1">{{ unit.supplier.name }}</p>
<p class="text-secondary-apple small mb-2">{{ unit.supplier.representative_name|default:"Контактное лицо не указано" }}</p>
<div class="d-flex flex-column gap-1">
<a href="tel:{{ unit.supplier.phone }}" class="text-decoration-none small text-dark"><i class="bi bi-telephone text-primary me-2"></i>{{ unit.supplier.phone|default:"-" }}</a>
<a href="mailto:{{ unit.supplier.email }}" class="text-decoration-none small text-dark"><i class="bi bi-envelope text-primary me-2"></i>{{ unit.supplier.email|default:"-" }}</a>
</div>
</div>
{% else %}
<div class="alert alert-light border-0 small">Поставщик не привязан к данной технике.</div>
{% endif %}
</div>
</div>
<div class="col-md-6 text-end">
<p class="text-muted small mb-1">Документы на авто</p>
<div class="col-md-5">
<h6 class="fw-bold mb-3">Документация</h6>
{% if unit.vehicle_documents %}
<a href="{{ unit.vehicle_documents.url }}" class="btn btn-outline-secondary btn-sm rounded-pill" target="_blank">
<i class="bi bi-file-earmark-text me-2"></i>Открыть документы
<a href="{{ unit.vehicle_documents.url }}" class="card p-4 text-center text-decoration-none text-dark hover-bg-light" target="_blank">
<i class="bi bi-file-earmark-pdf text-danger display-4 mb-2"></i>
<div class="fw-bold">Посмотреть ПТС/СТС</div>
<div class="small text-secondary-apple">Загруженный файл</div>
</a>
{% else %}
<p class="text-muted small">Не загружены</p>
<div class="text-center p-5 bg-light rounded-4 text-secondary-apple small">
Документы не загружены
</div>
{% endif %}
</div>
</div>
<hr class="my-4">
<h6 class="fw-bold mb-3">Связанные заявки на запчасти</h6>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Деталь</th>
<th>Статус</th>
<th>Дата</th>
<th></th>
</tr>
</thead>
<tbody>
{% for r in part_requests %}
<tr>
<td>{{ r.part_name }}</td>
<td><span class="badge bg-light text-dark border rounded-pill small">{{ r.get_status_display }}</span></td>
<td class="small">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="text-end">
{% if user.is_staff %}
<form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-center py-4 text-muted">Нет заявок</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 mt-4">
<div class="card-header bg-white fw-bold">Примечания</div>
<div class="card-body">
<p class="mb-0">{{ unit.notes|default:"Нет примечаний" }}</p>
</div>
<div class="card mt-4 p-4">
<h6 class="fw-bold mb-2">Заметки и примечания</h6>
<p class="text-secondary-apple mb-0">{{ unit.notes|default:"Дополнительная информация отсутствует." }}</p>
</div>
</div>
</div>
</div>
{% endblock %}
<style>
.nav-tabs .nav-link { color: var(--apple-text-secondary); border-radius: 0; padding: 1rem 1.5rem; margin-right: 1rem; transition: all 0.2s; }
.nav-tabs .nav-link.active { color: var(--apple-blue); border-bottom: 2px solid var(--apple-blue) !important; background: transparent; }
.hover-bg-light:hover { background-color: #f5f5f7 !important; transform: translateY(-2px); }
</style>
{% endblock %}

View File

@ -1,122 +1,128 @@
{% extends 'base.html' %}
{% block content %}
<div class="container py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow-sm border-0">
<div class="card-header bg-white border-0 py-4">
<h2 class="h4 mb-0 text-center">
{% if object %}Редактировать технику{% else %}Добавить новую технику{% endif %}
</h2>
<div class="card p-4 p-md-5">
<div class="text-center mb-5">
<h1 class="fw-bold mb-2">
{% if object %}Редактировать технику{% else %}Новая единица техники{% endif %}
</h1>
<p class="text-secondary-apple">Заполните данные для корректного учета и планирования ТО.</p>
</div>
<div class="card-body p-4">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- Section: Basic Info -->
<h5 class="mb-3 text-primary border-bottom pb-2">Основная информация</h5>
<div class="row g-3 mb-4">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- Section: Basic Info -->
<div class="mb-5">
<h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-info-circle me-2 text-primary"></i>Основная информация</h5>
<div class="row g-4">
<div class="col-md-6">
<label class="form-label">Наименование *</label>
<label class="form-label small fw-bold text-secondary-apple">Наименование *</label>
{{ form.name }}
</div>
<div class="col-md-6">
<label class="form-label">Модель *</label>
<label class="form-label small fw-bold text-secondary-apple">Модель *</label>
{{ form.model_name }}
</div>
<div class="col-md-4">
<label class="form-label">Категория</label>
<label class="form-label small fw-bold text-secondary-apple">Категория</label>
{{ form.category }}
</div>
<div class="col-md-4">
<label class="form-label">VIN / Серийный номер *</label>
<label class="form-label small fw-bold text-secondary-apple">VIN / Серийный номер *</label>
{{ form.vin }}
</div>
<div class="col-md-4">
<label class="form-label">Госномер</label>
<label class="form-label small fw-bold text-secondary-apple">Госномер</label>
{{ form.plate_number }}
</div>
<div class="col-md-4">
<label class="form-label">Год выпуска *</label>
<label class="form-label small fw-bold text-secondary-apple">Год выпуска *</label>
{{ form.year }}
</div>
<div class="col-md-4">
<label class="form-label">Дата ввода в эксплуатацию *</label>
<label class="form-label small fw-bold text-secondary-apple">Дата ввода в эксплуатацию *</label>
{{ form.commissioning_date }}
</div>
<div class="col-md-4">
<label class="form-label">Статус</label>
<label class="form-label small fw-bold text-secondary-apple">Статус</label>
{{ form.status }}
</div>
</div>
</div>
<!-- Section: Insurance -->
<h5 class="mb-3 text-primary border-bottom pb-2">Страховка</h5>
<div class="row g-3 mb-4">
<!-- Section: Insurance -->
<div class="mb-5">
<h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-shield-check me-2 text-primary"></i>Страхование</h5>
<div class="row g-4">
<div class="col-md-6">
<label class="form-label">Страховая компания</label>
<label class="form-label small fw-bold text-secondary-apple">Страховая компания</label>
{{ form.insurance_company }}
</div>
<div class="col-md-6">
<label class="form-label">Номер полиса</label>
<label class="form-label small fw-bold text-secondary-apple">Номер полиса</label>
{{ form.insurance_policy_number }}
</div>
<div class="col-md-6">
<label class="form-label">Дата начала</label>
<label class="form-label small fw-bold text-secondary-apple">Дата начала</label>
{{ form.insurance_start_date }}
</div>
<div class="col-md-6">
<label class="form-label">Дата окончания</label>
<label class="form-label small fw-bold text-secondary-apple">Дата окончания</label>
{{ form.insurance_end_date }}
</div>
</div>
</div>
<!-- Section: Supply -->
<h5 class="mb-3 text-primary border-bottom pb-2">Снабжение</h5>
<div class="row g-3 mb-4">
<!-- Section: Supply -->
<div class="mb-5">
<h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-box-seam me-2 text-primary"></i>Снабжение</h5>
<div class="row g-4">
<div class="col-md-6">
<label class="form-label d-flex justify-content-between">
Поставщик / Контрагент
<a href="{% url 'supplier_create' %}" target="_blank" class="small text-decoration-none">
<i class="bi bi-plus-circle"></i> Создать нового
<label class="form-label d-flex justify-content-between small fw-bold text-secondary-apple">
Поставщик
<a href="{% url 'supplier_create' %}" target="_blank" class="fw-normal text-decoration-none">
+ Добавить в базу
</a>
</label>
{{ form.supplier }}
<div class="form-text">Выберите поставщика из списка или создайте нового (откроется в новой вкладке).</div>
</div>
<div class="col-md-6">
<label class="form-label">Номер договора</label>
<label class="form-label small fw-bold text-secondary-apple">Номер договора</label>
{{ form.contract_number }}
<div class="form-text">Номер договора по данной единице техники.</div>
</div>
<div class="col-md-6">
<label class="form-label">Документы на авто (PDF/Image)</label>
<div class="col-md-12">
<label class="form-label small fw-bold text-secondary-apple">Документы на авто (PDF/Image)</label>
{{ form.vehicle_documents }}
</div>
</div>
</div>
<!-- Section: Media & Notes -->
<h5 class="mb-3 text-primary border-bottom pb-2">Медиа и примечания</h5>
<div class="row g-3 mb-4">
<!-- Section: Media & Notes -->
<div class="mb-5">
<h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-camera me-2 text-primary"></i>Медиа</h5>
<div class="row g-4">
<div class="col-md-6">
<label class="form-label">Фото техники</label>
<label class="form-label small fw-bold text-secondary-apple">Фотографии техники</label>
{{ form.photo }}
</div>
<div class="col-12">
<label class="form-label">Примечания</label>
<label class="form-label small fw-bold text-secondary-apple">Примечания</label>
{{ form.notes }}
</div>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end mt-5">
<a href="{% if object %}{% url 'fleet_detail' object.pk %}{% else %}{% url 'fleet_list' %}{% endif %}" class="btn btn-light px-4">Отмена</a>
<button type="submit" class="btn btn-primary px-5">Сохранить</button>
</div>
</form>
</div>
<div class="d-flex justify-content-end gap-3 mt-4 pt-4 border-top">
<a href="{% if object %}{% url 'fleet_detail' object.pk %}{% else %}{% url 'fleet_list' %}{% endif %}" class="btn btn-link text-decoration-none text-secondary-apple">Отмена</a>
<button type="submit" class="btn btn-primary px-5">Сохранить</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -3,95 +3,81 @@
{% block title %}Список техники | Fleet Manager{% endblock %}
{% block content %}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3">
<div>
<h2 class="mb-1">Парк техники</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'index' %}" class="text-decoration-none">Дашборд</a></li>
<li class="breadcrumb-item active" aria-current="page">Техника</li>
</ol>
</nav>
<div class="row mb-5">
<div class="col-md-8">
<h1 class="display-6 fw-bold mb-2">Парк техники</h1>
<p class="text-secondary-apple">Управление и мониторинг каждой единицы оборудования.</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
</div>
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
</div>
<!-- Filters -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
<input type="text" name="search" class="form-control border-start-0" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
</div>
<div class="card p-4 mb-5">
<form method="get" class="row g-3 align-items-center">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search text-secondary-apple"></i></span>
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
</div>
<div class="col-md-4">
<select name="status" class="form-select" onchange="this.form.submit()">
<option value="">Все статусы</option>
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option>
<option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option>
<option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option>
<option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option>
<option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-outline-secondary w-100">Применить</button>
</div>
</form>
</div>
</div>
<div class="col-md-4">
<select name="status" class="form-select" onchange="this.form.submit()">
<option value="">Все статусы</option>
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option>
<option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option>
<option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option>
<option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option>
<option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-outline-primary w-100">Фильтровать</button>
</div>
</form>
</div>
<div class="row g-4">
{% for unit in units %}
<div class="col-12 col-md-6 col-xl-4">
<div class="card h-100 shadow-sm border-0">
<div class="row g-0 h-100">
<div class="col-4">
{% if unit.photo %}
<img src="{{ unit.photo.url }}" class="img-fluid rounded-start h-100 object-fit-cover" alt="{{ unit.name }}" style="min-height: 160px;">
{% else %}
<div class="bg-light rounded-start h-100 d-flex align-items-center justify-content-center border-end" style="min-height: 160px;">
<i class="bi bi-truck text-muted display-6"></i>
</div>
{% endif %}
<div class="col-12 col-md-6 col-lg-4">
<div class="card h-100">
{% if unit.photo %}
<img src="{{ unit.photo.url }}" class="card-img-top object-fit-cover" alt="{{ unit.name }}" style="height: 200px;">
{% else %}
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="bi bi-truck text-secondary-apple display-4"></i>
</div>
<div class="col-8">
<div class="card-body d-flex flex-column h-100">
<div class="d-flex justify-content-between align-items-start mb-2">
<span class="badge bg-{{ unit.get_status_color }} bg-opacity-10 text-{{ unit.get_status_color }} border border-{{ unit.get_status_color }}">
{{ unit.get_status_display }}
</span>
<span class="text-muted small">#{{ unit.pk }}</span>
</div>
<h5 class="card-title mb-1">
<a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-slate-900">{{ unit.name }}</a>
</h5>
<p class="card-text text-muted small mb-3">
{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}
</p>
<div class="mt-auto d-flex justify-content-between align-items-center">
<div class="small text-muted">
<i class="bi bi-calendar3 me-1"></i>{{ unit.year }}
</div>
<a href="{{ unit.get_absolute_url }}" class="btn btn-sm btn-outline-primary">Подробнее</a>
</div>
{% endif %}
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start mb-3">
<span class="badge bg-{{ unit.get_status_color }} text-white">
{{ unit.get_status_display }}
</span>
<span class="text-secondary-apple small">#{{ unit.pk }}</span>
</div>
<h5 class="fw-bold mb-1">
<a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ unit.name }}</a>
</h5>
<p class="text-secondary-apple small mb-4">
{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}
</p>
<div class="d-flex justify-content-between align-items-center pt-3 border-top">
<div class="small text-secondary-apple">
<i class="bi bi-calendar3 me-1"></i>{{ unit.year }}
</div>
<a href="{{ unit.get_absolute_url }}" class="btn btn-sm btn-link text-decoration-none p-0">Подробнее <i class="bi bi-chevron-right small"></i></a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<div class="mb-3">
<i class="bi bi-inbox text-muted display-1"></i>
</div>
<h4 class="text-muted">Техника не найдена</h4>
<p class="text-muted">Попробуйте изменить параметры поиска или добавить новую единицу.</p>
<a href="{% url 'fleet_add' %}" class="btn btn-primary mt-2">Добавить технику</a>
<i class="bi bi-inbox text-secondary-apple display-1 mb-3"></i>
<h4 class="fw-bold">Техника не найдена</h4>
<p class="text-secondary-apple">Попробуйте изменить параметры поиска.</p>
</div>
{% endfor %}
</div>
@ -99,21 +85,19 @@
<!-- Pagination -->
{% if is_paginated %}
<nav class="mt-5">
<ul class="pagination justify-content-center">
<ul class="pagination justify-content-center border-0">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Назад</a></li>
<li class="page-item"><a class="page-link border-0 rounded-3 mx-1 text-dark" href="?page={{ page_obj.previous_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"><i class="bi bi-chevron-left"></i></a></li>
{% endif %}
{% for i in paginator.page_range %}
<li class="page-item {% if page_obj.number == i %}active{% endif %}">
<a class="page-link" href="?page={{ i }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">{{ i }}</a>
</li>
<li class="page-item"><a class="page-link border-0 rounded-3 mx-1 {% if page_obj.number == i %}bg-primary text-white{% else %}text-dark{% endif %}" href="?page={{ i }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">{{ i }}</a></li>
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Вперед</a></li>
<li class="page-item"><a class="page-link border-0 rounded-3 mx-1 text-dark" href="?page={{ page_obj.next_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"><i class="bi bi-chevron-right"></i></a></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -2,59 +2,50 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="container">
<div class="row mb-5 text-center">
<div class="col-12">
<h1 class="display-5 fw-bold mb-2">Обзор парка</h1>
<p class="text-secondary-apple">Вся информация о вашей технике в одном месте.</p>
</div>
</div>
<!-- Stats Row -->
<div class="row g-4 mb-4">
<div class="row g-4 mb-5">
<div class="col-md-3">
<div class="card border-0 shadow-sm bg-slate-900 text-white p-3 h-100">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-blue-500 p-3 me-3">
<i class="bi bi-truck fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0">{{ total_units }}</h3>
<span class="text-white-50 small">Всего техники</span>
</div>
<div class="card p-4 h-100 text-center">
<div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 56px; height: 56px;">
<i class="bi bi-truck fs-3"></i>
</div>
<h3 class="fw-bold mb-1">{{ total_units }}</h3>
<span class="text-secondary-apple small">Всего техники</span>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-success-subtle p-3 me-3 text-success">
<i class="bi bi-check-circle fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-success">{{ active_units }}</h3>
<span class="text-muted small">В работе</span>
</div>
<div class="card p-4 h-100 text-center">
<div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-success-subtle text-success" style="width: 56px; height: 56px;">
<i class="bi bi-check-circle fs-3"></i>
</div>
<h3 class="fw-bold mb-1 text-success">{{ active_units }}</h3>
<span class="text-secondary-apple small">В работе</span>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-danger-subtle p-3 me-3 text-danger">
<i class="bi bi-exclamation-triangle fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-danger">{{ broken_units }}</h3>
<span class="text-muted small">Сломано</span>
</div>
<div class="card p-4 h-100 text-center">
<div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-danger-subtle text-danger" style="width: 56px; height: 56px;">
<i class="bi bi-exclamation-triangle fs-3"></i>
</div>
<h3 class="fw-bold mb-1 text-danger">{{ broken_units }}</h3>
<span class="text-secondary-apple small">Сломано</span>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-warning-subtle p-3 me-3 text-warning">
<i class="bi bi-wrench fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-warning">{{ repair_units }}</h3>
<span class="text-muted small">В ремонте</span>
</div>
<div class="card p-4 h-100 text-center">
<div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-warning-subtle text-warning" style="width: 56px; height: 56px;">
<i class="bi bi-wrench fs-3"></i>
</div>
<h3 class="fw-bold mb-1 text-warning">{{ repair_units }}</h3>
<span class="text-secondary-apple small">В ремонте</span>
</div>
</div>
</div>
@ -62,53 +53,53 @@
<div class="row g-4">
<!-- Recent Activities -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="fw-bold mb-0">Ближайшие ТО</h6>
<a href="{% url 'maintenance_list' %}" class="btn btn-sm btn-link">Все ТО</a>
<a href="{% url 'maintenance_list' %}" class="btn btn-sm btn-link text-decoration-none p-0">Все ТО <i class="bi bi-chevron-right small"></i></a>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<thead>
<tr>
<th>Техника</th>
<th class="ps-4">Техника</th>
<th>Тип</th>
<th>Дата</th>
<th>Статус</th>
<th class="pe-4 text-end">Статус</th>
</tr>
</thead>
<tbody>
{% for m in recent_maintenances %}
<tr>
<td><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none fw-bold">{{ m.fleet_unit.name }}</a></td>
<td class="ps-4"><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none fw-bold text-dark">{{ m.fleet_unit.name }}</a></td>
<td>{{ m.m_type }}</td>
<td>{{ m.planned_date }}</td>
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill small">{{ m.get_status_display }}</span></td>
<td class="pe-4 text-end"><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">{{ m.get_status_display }}</span></td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-center py-4 text-muted">Нет запланированных ТО</td></tr>
<tr><td colspan="4" class="text-center py-5 text-secondary-apple">Нет запланированных ТО</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="fw-bold mb-0">Последние поломки</h6>
<a href="{% url 'breakdown_list' %}" class="btn btn-sm btn-link">Весь журнал</a>
<a href="{% url 'breakdown_list' %}" class="btn btn-sm btn-link text-decoration-none p-0">Весь журнал <i class="bi bi-chevron-right small"></i></a>
</div>
<div class="list-group list-group-flush">
{% for b in recent_breakdowns %}
<div class="list-group-item d-flex justify-content-between align-items-center py-3">
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<div class="fw-bold text-danger">{{ b.system_node }}</div>
<div class="small text-muted">{{ b.fleet_unit.name }} — {{ b.date|date:"d.m.Y" }}</div>
<div class="small text-secondary-apple">{{ b.fleet_unit.name }} — {{ b.date|date:"d.m.Y" }}</div>
</div>
<span class="badge bg-light text-dark border">{{ b.get_status_display }}</span>
<span class="badge bg-light text-dark border-0 shadow-sm">{{ b.get_status_display }}</span>
</div>
{% empty %}
<div class="list-group-item text-center py-4 text-muted">Поломок не зафиксировано</div>
<div class="list-group-item text-center py-5 text-secondary-apple">Поломок не зафиксировано</div>
{% endfor %}
</div>
</div>
@ -116,45 +107,39 @@
<!-- Quick Actions & Search -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h6 class="fw-bold mb-3">Быстрый поиск</h6>
<form action="{% url 'fleet_list' %}" method="get">
<div class="input-group mb-3">
<input type="text" name="search" class="form-control border-end-0" placeholder="Госномер или VIN...">
<button class="btn btn-outline-secondary border-start-0" type="submit">
<i class="bi bi-search"></i>
</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h6 class="fw-bold mb-3">Быстрые действия</h6>
<div class="d-grid gap-2">
<a href="{% url 'fleet_add' %}" class="btn btn-primary py-2 rounded-pill">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
<a href="{% url 'maintenance_add' %}" class="btn btn-outline-primary py-2 rounded-pill">
<i class="bi bi-calendar-plus me-2"></i>Запланировать ТО
</a>
<a href="{% url 'breakdown_add' %}" class="btn btn-outline-danger py-2 rounded-pill">
<i class="bi bi-exclamation-circle me-2"></i>Заявить о поломке
</a>
<div class="card mb-4 p-4">
<h6 class="fw-bold mb-3">Поиск</h6>
<form action="{% url 'fleet_list' %}" method="get">
<div class="input-group">
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search text-secondary-apple"></i></span>
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Госномер или VIN...">
</div>
</form>
</div>
<div class="card mb-4 p-4">
<h6 class="fw-bold mb-4">Действия</h6>
<div class="d-grid gap-3">
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
<a href="{% url 'maintenance_add' %}" class="btn btn-outline-primary">
<i class="bi bi-calendar-plus me-2"></i>Запланировать ТО
</a>
<a href="{% url 'breakdown_add' %}" class="btn btn-outline-danger">
<i class="bi bi-exclamation-circle me-2"></i>Заявить о поломке
</a>
</div>
</div>
<div class="card border-0 shadow-sm bg-blue-50">
<div class="card-body">
<h6 class="fw-bold text-blue-900 mb-2">Мобильное приложение</h6>
<p class="small text-blue-800 mb-3">Приложение оптимизировано для работы механиков с телефона. Используйте QR-коды для быстрого доступа к чек-листам.</p>
<i class="bi bi-phone-vibrate text-blue-500 display-4 d-block text-center"></i>
<div class="card p-4 border-0" style="background: linear-gradient(135deg, #eef2f3 0%, #8e9eab 100%);">
<h6 class="fw-bold mb-2">Мобильная версия</h6>
<p class="small text-secondary-apple mb-3">Используйте камеру телефона для сканирования QR-кодов на технике для быстрого доступа.</p>
<div class="text-center">
<i class="bi bi-phone fs-1"></i>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -2,132 +2,200 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="row">
<div class="container">
<div class="row mb-5">
<div class="col-md-8">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-2">
<li class="breadcrumb-item"><a href="{% url 'maintenance_list' %}" class="text-decoration-none text-secondary-apple">ТО</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ maintenance.m_type }}</li>
</ol>
</nav>
<h1 class="display-6 fw-bold mb-0">{{ maintenance.m_type }}</h1>
<p class="text-secondary-apple">{{ maintenance.fleet_unit.name }} | {{ maintenance.planned_date }}</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<span class="badge bg-{{ maintenance.status|yesno:'success,warning,secondary' }} text-white px-3 py-2 fs-6">
{{ maintenance.get_status_display }}
</span>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
<div class="card shadow-sm mb-4 border-0">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">
<i class="bi bi-tools text-primary me-2"></i>
{{ maintenance.m_type }} - {{ maintenance.fleet_unit.name }}
</h5>
<span class="badge bg-{{ maintenance.status|yesno:'success,warning,secondary' }} rounded-pill px-3">
{% if maintenance.status == 'planned' %}Планируется
{% elif maintenance.status == 'in_progress' %}В процессе
{% else %}Выполнено{% endif %}
</span>
<div class="card p-4 mb-4">
<h5 class="fw-bold mb-4">Детали обслуживания</h5>
<div class="row mb-4">
<div class="col-md-6">
<p class="text-secondary-apple small mb-1">Техника</p>
<p class="fw-bold mb-3"><a href="{{ maintenance.fleet_unit.get_absolute_url }}" class="text-dark">{{ maintenance.fleet_unit }}</a></p>
<p class="text-secondary-apple small mb-1">Плановая дата</p>
<p class="fw-bold mb-3">{{ maintenance.planned_date }}</p>
</div>
<div class="col-md-6">
<p class="text-secondary-apple small mb-1">Исполнитель</p>
<p class="fw-bold mb-3">{{ maintenance.mechanic|default:"Не назначен" }}</p>
<p class="text-secondary-apple small mb-1">Плановый пробег</p>
<p class="fw-bold mb-3">{{ maintenance.planned_runtime }}</p>
</div>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<p class="text-muted small mb-1">Техника</p>
<p class="fw-bold mb-3"><a href="{{ maintenance.fleet_unit.get_absolute_url }}">{{ maintenance.fleet_unit }}</a></p>
<p class="text-muted small mb-1">Плановая дата</p>
<p class="fw-bold mb-3">{{ maintenance.planned_date }}</p>
</div>
<div class="col-md-6">
<p class="text-muted small mb-1">Исполнитель</p>
<p class="fw-bold mb-3">{{ maintenance.mechanic|default:"Не назначен" }}</p>
<p class="text-muted small mb-1">Плановый пробег/моточасы</p>
<p class="fw-bold mb-3">{{ maintenance.planned_runtime }}</p>
</div>
</div>
<hr class="my-4">
<hr class="my-4 opacity-50">
<h6 class="fw-bold mb-3">Чек-лист операций</h6>
<div class="list-group list-group-flush border rounded mb-4">
{% for item in maintenance.checklist %}
<div class="list-group-item d-flex align-items-center py-3">
<form action="{% url 'maintenance_process' maintenance.pk %}" method="post" class="d-flex align-items-center w-100">
{% csrf_token %}
<input type="hidden" name="task_index" value="{{ forloop.counter0 }}">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="done" value="true"
id="task_{{ forloop.counter0 }}"
{% if item.done %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label {% if item.done %}text-decoration-line-through text-muted{% endif %}" for="task_{{ forloop.counter0 }}">
{{ item.task }}
</label>
</div>
</form>
</div>
{% empty %}
<div class="list-group-item text-muted text-center py-4">Чек-лист пуст</div>
{% endfor %}
</div>
{% if maintenance.status != 'completed' %}
<div class="mt-4 pt-3 border-top">
<h6 class="fw-bold mb-3">Завершение ТО</h6>
<form action="{% url 'maintenance_complete' maintenance.pk %}" method="post" enctype="multipart/form-data">
<h5 class="fw-bold mb-4">Чек-лист операций</h5>
<div class="list-group list-group-flush mb-5">
{% for item in maintenance.checklist %}
<div class="list-group-item d-flex align-items-center py-3 border-0 border-bottom">
<form action="{% url 'maintenance_process' maintenance.pk %}" method="post" class="d-flex align-items-center w-100">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-6">
<label class="form-label small fw-bold">Фактическая дата</label>
<input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">Фактический пробег/моточасы</label>
<input type="number" name="actual_runtime" class="form-control" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">Использованные запчасти</label>
<textarea name="parts_used" class="form-control" rows="2"></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-success w-100 py-2 fw-bold">
<i class="bi bi-check-circle me-2"></i>Завершить ТО и обновить статус техники
</button>
</div>
<input type="hidden" name="task_index" value="{{ forloop.counter0 }}">
<div class="form-check">
<input class="form-check-input border-secondary-apple" type="checkbox" name="done" value="true"
id="task_{{ forloop.counter0 }}"
{% if item.done %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label {% if item.done %}text-decoration-line-through text-secondary-apple{% endif %}" for="task_{{ forloop.counter0 }}">
{{ item.task }}
</label>
</div>
</form>
</div>
{% else %}
<div class="mt-4 pt-3 border-top">
<div class="alert alert-success d-flex align-items-center">
<i class="bi bi-info-circle-fill me-2"></i>
<div>ТО успешно завершено {{ maintenance.actual_date }}</div>
</div>
<div class="row">
{% empty %}
<div class="text-center py-4 text-secondary-apple">Чек-лист пуст</div>
{% endfor %}
</div>
<h5 class="fw-bold mb-4">Расходные материалы</h5>
<div class="table-responsive mb-4">
<table class="table align-middle">
<thead>
<tr>
<th>Наименование</th>
<th>Артикул</th>
<th>Кол-во</th>
<th width="50"></th>
</tr>
</thead>
<tbody>
{% for part in maintenance.used_parts.all %}
<tr>
<td class="fw-bold">{{ part.part_name }}</td>
<td class="small">{{ part.article_number|default:"-" }}</td>
<td>{{ part.quantity }}</td>
<td class="text-end">
<form action="{% url 'maintenance_part_delete' part.pk %}" method="post" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0" onclick="return confirm('Удалить?')">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center text-secondary-apple py-4">Расходники не добавлены</td>
</tr>
{% endfor %}
{% if maintenance.status != 'completed' %}
<tr class="table-light border-0">
<td colspan="4" class="p-3">
<form action="{% url 'maintenance_part_add' maintenance.pk %}" method="post" class="row g-2">
{% csrf_token %}
<div class="col-md-5">{{ part_form.part_name }}</div>
<div class="col-md-3">{{ part_form.article_number }}</div>
<div class="col-md-4">
<div class="input-group">
{{ part_form.quantity }}
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</form>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{% if maintenance.status != 'completed' %}
<div class="mt-5 p-4 bg-light rounded-4">
<h5 class="fw-bold mb-4">Завершение работ</h5>
<form action="{% url 'maintenance_complete' maintenance.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row g-4">
<div class="col-md-6">
<p class="text-muted small mb-1">Фактический пробег/моточасы</p>
<p class="fw-bold">{{ maintenance.actual_runtime }}</p>
<label class="form-label small fw-bold">Фактическая дата</label>
<input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required>
</div>
<div class="col-md-6">
<p class="text-muted small mb-1">Запчасти</p>
<p>{{ maintenance.parts_used|default:"-" }}</p>
<label class="form-label small fw-bold">Фактический пробег</label>
<input type="number" name="actual_runtime" class="form-control" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">Примечания по работам</label>
<textarea name="parts_used" class="form-control" rows="3" placeholder="Опишите дополнительные детали..."></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold">
<i class="bi bi-check-circle me-2"></i>Завершить техническое обслуживание
</button>
</div>
</div>
<a href="{% url 'maintenance_pdf' maintenance.pk %}" class="btn btn-outline-primary btn-sm mt-2">
<i class="bi bi-file-pdf me-1"></i>Скачать Акт ТО (PDF)
</a>
</form>
</div>
{% else %}
<div class="mt-5 pt-4 border-top">
<div class="alert alert-success rounded-4 d-flex align-items-center mb-4">
<i class="bi bi-check2-circle fs-4 me-3"></i>
<div>
<div class="fw-bold">ТО успешно выполнено</div>
<div class="small">Дата: {{ maintenance.actual_date }} | Пробег: {{ maintenance.actual_runtime }}</div>
</div>
</div>
{% if maintenance.parts_used %}
<div class="mb-4">
<p class="text-secondary-apple small mb-1">Доп. работы</p>
<p class="fw-bold">{{ maintenance.parts_used }}</p>
</div>
{% endif %}
<a href="{% url 'maintenance_pdf' maintenance.pk %}" class="btn btn-outline-primary">
<i class="bi bi-file-earmark-pdf me-2"></i>Скачать PDF-отчет
</a>
</div>
{% endif %}
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white fw-bold py-3">Примечания</div>
<div class="card-body">
<p class="mb-0">{{ maintenance.notes|default:"Нет примечаний" }}</p>
<div class="card p-4 mb-4">
<h6 class="fw-bold mb-3">Техника</h6>
<div class="d-flex align-items-center mb-4">
{% if maintenance.fleet_unit.photo %}
<img src="{{ maintenance.fleet_unit.photo.url }}" class="rounded-3 me-3" style="width: 60px; height: 60px; object-fit: cover;">
{% endif %}
<div>
<div class="fw-bold">{{ maintenance.fleet_unit.name }}</div>
<div class="small text-secondary-apple">{{ maintenance.fleet_unit.model_name }}</div>
</div>
</div>
<div class="row g-2 small">
<div class="col-5 text-secondary-apple">VIN:</div>
<div class="col-7 fw-bold">{{ maintenance.fleet_unit.vin }}</div>
<div class="col-5 text-secondary-apple">Госномер:</div>
<div class="col-7 fw-bold">{{ maintenance.fleet_unit.plate_number|default:"-" }}</div>
</div>
</div>
{% if maintenance.fleet_unit.photo %}
<div class="card shadow-sm border-0">
<img src="{{ maintenance.fleet_unit.photo.url }}" class="card-img-top" alt="{{ maintenance.fleet_unit.name }}">
<div class="card-body">
<p class="card-text small text-muted text-center">{{ maintenance.fleet_unit.name }}</p>
</div>
<div class="card p-4">
<h6 class="fw-bold mb-3">Исходные примечания</h6>
<p class="text-secondary-apple small mb-0">{{ maintenance.notes|default:"Примечания отсутствуют" }}</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -2,51 +2,54 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="fw-bold mb-0">Техническое обслуживание</h3>
<a href="{% url 'maintenance_add' %}" class="btn btn-primary rounded-pill px-4">
<i class="bi bi-plus-lg me-2"></i>Запланировать ТО
</a>
<div class="container">
<div class="row mb-5">
<div class="col-md-8">
<h1 class="display-6 fw-bold mb-2">Техническое обслуживание</h1>
<p class="text-secondary-apple">Планирование и контроль регламентных работ.</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'maintenance_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Запланировать ТО
</a>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card mb-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<thead>
<tr>
<th class="border-0 px-4">Техника</th>
<th class="border-0">Тип</th>
<th class="border-0">Плановая дата</th>
<th class="border-0">Статус</th>
<th class="border-0">Исполнитель</th>
<th class="border-0 text-end px-4">Действия</th>
<th class="ps-4">Техника</th>
<th>Тип</th>
<th>Дата</th>
<th>Статус</th>
<th>Механик</th>
<th class="pe-4 text-end">Действия</th>
</tr>
</thead>
<tbody>
{% for m in maintenances %}
<tr>
<td class="px-4">
<div class="fw-bold"><a href="{{ m.fleet_unit.get_absolute_url }}">{{ m.fleet_unit.name }}</a></div>
<div class="small text-muted">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div>
<td class="ps-4">
<div class="fw-bold"><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ m.fleet_unit.name }}</a></div>
<div class="small text-secondary-apple">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div>
</td>
<td><span class="badge bg-light text-dark border">{{ m.get_m_type_display }}</span></td>
<td>{{ m.get_m_type_display }}</td>
<td>{{ m.planned_date }}</td>
<td>
<span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill">
{% if m.status == 'planned' %}Планируется
{% elif m.status == 'in_progress' %}В процессе
{% else %}Выполнено{% endif %}
<span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">
{{ m.get_status_display }}
</span>
</td>
<td>{{ m.mechanic|default:"-" }}</td>
<td class="text-end px-4">
<div class="btn-group">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">Открыть</a>
<td class="small">{{ m.mechanic|default:"-" }}</td>
<td class="pe-4 text-end">
<div class="d-flex justify-content-end align-items-center">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none me-2">Открыть</a>
{% if user.is_staff %}
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger rounded-pill px-2" title="Удалить"><i class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</div>
@ -54,7 +57,7 @@
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">Нет запланированных ТО</td>
<td colspan="6" class="text-center py-5 text-secondary-apple">Нет запланированных ТО</td>
</tr>
{% endfor %}
</tbody>
@ -62,4 +65,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -2,37 +2,42 @@
{% load static %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="fw-bold mb-0">Заявки на запчасти</h3>
<a href="{% url 'part_request_add' %}" class="btn btn-primary rounded-pill px-4">
<i class="bi bi-plus-lg me-2"></i>Новая заявка
</a>
<div class="container">
<div class="row mb-5">
<div class="col-md-8">
<h1 class="display-6 fw-bold mb-2">Заявки на запчасти</h1>
<p class="text-secondary-apple">Отслеживание поставок и управление запасами.</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'part_request_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Новая заявка
</a>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card mb-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<thead>
<tr>
<th class="border-0 px-4">Техника</th>
<th class="border-0">Деталь</th>
<th class="border-0">Артикул</th>
<th class="border-0">Кол-во</th>
<th class="border-0">Статус</th>
<th class="border-0">Дата создания</th>
<th class="border-0 text-end px-4">Действия</th>
<th class="ps-4">Техника</th>
<th>Деталь</th>
<th>Артикул</th>
<th>Кол-во</th>
<th>Статус</th>
<th>Дата</th>
<th class="pe-4 text-end">Действия</th>
</tr>
</thead>
<tbody>
{% for r in requests %}
<tr>
<td class="px-4">
<td class="ps-4">
<div class="fw-bold">{{ r.fleet_unit.name }}</div>
<div class="small text-muted">{{ r.fleet_unit.plate_number|default:r.fleet_unit.vin }}</div>
<div class="small text-secondary-apple">{{ r.fleet_unit.plate_number|default:r.fleet_unit.vin }}</div>
</td>
<td>{{ r.part_name }}</td>
<td>{{ r.article_number|default:"-" }}</td>
<td class="fw-bold text-dark">{{ r.part_name }}</td>
<td class="small">{{ r.article_number|default:"-" }}</td>
<td>{{ r.quantity }}</td>
<td>
{% if r.status == 'draft' %}<span class="badge bg-secondary">Черновик</span>
@ -41,24 +46,24 @@
{% elif r.status == 'delivered' %}<span class="badge bg-success">Доставлено</span>
{% endif %}
</td>
<td class="small">{{ r.created_at|date:"d.m.Y H:i" }}</td>
<td class="text-end px-4">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle rounded-pill px-3" data-bs-toggle="dropdown">
Действие
<td class="small text-secondary-apple">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="pe-4 text-end">
<div class="dropdown">
<button class="btn btn-sm btn-link text-decoration-none dropdown-toggle no-caret" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0">
<li><a class="dropdown-item" href="#">Изменить статус</a></li>
<li><a class="dropdown-item" href="#">Экспорт Excel</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#">Удалить</a></li>
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 rounded-3">
<li><a class="dropdown-item py-2" href="#"><i class="bi bi-pencil me-2"></i>Изменить</a></li>
<li><a class="dropdown-item py-2" href="#"><i class="bi bi-check2-circle me-2"></i>Обновить статус</a></li>
<li><hr class="dropdown-divider opacity-50"></li>
<li><a class="dropdown-item py-2 text-danger" href="#"><i class="bi bi-trash me-2"></i>Удалить</a></li>
</ul>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center py-5 text-muted">Нет активных заявок</td>
<td colspan="7" class="text-center py-5 text-secondary-apple">Нет активных заявок</td>
</tr>
{% endfor %}
</tbody>
@ -66,4 +71,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,116 +1,112 @@
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h2">Снабжение</h1>
<div class="container">
<div class="row mb-5">
<div class="col-12 text-center">
<h1 class="display-5 fw-bold mb-2">Снабжение</h1>
<p class="text-secondary-apple">Управление закупками и работа с контрагентами.</p>
</div>
</div>
<div class="row">
<div class="row g-4">
<!-- Part Requests Section -->
<div class="col-lg-8">
<div class="card shadow-sm mb-4">
<div class="card-header bg-white py-3">
<h5 class="card-title mb-0">Заявки на запчасти</h5>
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0">Заявки на запчасти</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>Дата</th>
<th>Техника</th>
<th>Деталь</th>
<th>Кол-во</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
{% for req in requests %}
<tr>
<td>{{ req.created_at|date:"d.m.Y" }}</td>
<td>
<a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none">
{{ req.fleet_unit.name }}
</a>
</td>
<td>{{ req.part_name }}</td>
<td>{{ req.quantity }}</td>
<td>
<span class="badge rounded-pill bg-{{ req.status|yesno:'info,secondary' }}">
{{ req.get_status_display }}
</span>
</td>
<td>
<div class="btn-group">
<a href="#" class="btn btn-sm btn-outline-primary">Управление</a>
{% if user.is_staff %}
<form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger ms-1" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-4">Нет активных заявок</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th class="ps-4">Дата</th>
<th>Техника</th>
<th>Деталь</th>
<th>Статус</th>
<th class="pe-4 text-end">Действия</th>
</tr>
</thead>
<tbody>
{% for req in requests %}
<tr>
<td class="ps-4 small text-secondary-apple">{{ req.created_at|date:"d.m.Y" }}</td>
<td>
<a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none fw-bold text-dark">
{{ req.fleet_unit.name }}
</a>
</td>
<td>{{ req.part_name }} <span class="text-secondary-apple small">x{{ req.quantity }}</span></td>
<td>
<span class="badge bg-light text-dark border-0 shadow-sm">
{{ req.get_status_display }}
</span>
</td>
<td class="pe-4 text-end">
<div class="d-flex justify-content-end align-items-center">
<a href="#" class="btn btn-sm btn-link text-decoration-none me-2">Управление</a>
{% if user.is_staff %}
<form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-5 text-secondary-apple">Активных заявок нет</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Suppliers Section -->
<div class="col-lg-4">
<div class="card shadow-sm">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Поставщики</h5>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0">Контрагенты</h5>
<a href="{% url 'supplier_create' %}" class="btn btn-sm btn-primary">
<i class="bi bi-plus-lg"></i> Добавить
<i class="bi bi-plus-lg"></i>
</a>
</div>
<div class="card-body">
<div class="list-group list-group-flush">
{% for supplier in suppliers %}
<div class="list-group-item px-0 py-3">
<div class="d-flex w-100 justify-content-between align-items-center">
<h6 class="mb-1">{{ supplier.name }}</h6>
<div class="btn-group">
<a href="{% url 'supplier_edit' supplier.pk %}" class="btn btn-sm btn-link p-0 text-muted me-2" title="Редактировать">
<i class="bi bi-pencil-square"></i>
</a>
{% if user.is_staff %}
<form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link p-0 text-danger" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</div>
<div class="list-group list-group-flush">
{% for supplier in suppliers %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between align-items-start mb-2">
<h6 class="fw-bold mb-0 text-dark">{{ supplier.name }}</h6>
<div class="d-flex gap-2">
<a href="{% url 'supplier_edit' supplier.pk %}" class="text-secondary-apple" title="Редактировать">
<i class="bi bi-pencil-square"></i>
</a>
{% if user.is_staff %}
<form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0" title="Удалить">
<i class="bi bi-trash"></i>
</button>
</form>
{% endif %}
</div>
<p class="mb-1 small text-muted">
<strong>Менеджер:</strong> {{ supplier.representative_name|default:"-" }}<br>
<strong>Тел:</strong> {{ supplier.phone|default:"-" }}<br>
<strong>Email:</strong> {{ supplier.email|default:"-" }}<br>
<strong>Договор:</strong> {{ supplier.contract_number|default:"-" }}
</p>
</div>
{% empty %}
<div class="text-center py-3 text-muted">Список поставщиков пуст</div>
{% endfor %}
<div class="small text-secondary-apple">
<div class="mb-1"><i class="bi bi-person me-2"></i>{{ supplier.representative_name|default:"-" }}</div>
<div class="mb-1"><i class="bi bi-telephone me-2"></i>{{ supplier.phone|default:"-" }}</div>
<div class="mb-1"><i class="bi bi-file-text me-2"></i>Договор: {{ supplier.contract_number|default:"-" }}</div>
</div>
</div>
{% empty %}
<div class="text-center py-5 text-secondary-apple">Список пуст</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -20,6 +20,8 @@ urlpatterns = [
path('maintenance/<int:pk>/process/', views.MaintenanceProcessView.as_view(), name='maintenance_process'),
path('maintenance/<int:pk>/complete/', views.MaintenanceCompleteView.as_view(), name='maintenance_complete'),
path('maintenance/<int:pk>/pdf/', views.MaintenancePDFView.as_view(), name='maintenance_pdf'),
path('maintenance/<int:pk>/part/add/', views.MaintenancePartAddView.as_view(), name='maintenance_part_add'),
path('maintenance-part/<int:pk>/delete/', views.MaintenancePartDeleteView.as_view(), name='maintenance_part_delete'),
# Breakdown
path('breakdown/', views.BreakdownListView.as_view(), name='breakdown_list'),

View File

@ -11,7 +11,7 @@ from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier
from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier, MaintenancePart
# Mixins
class StaffRequiredMixin(UserPassesTestMixin):
@ -73,6 +73,16 @@ class MaintenanceForm(forms.ModelForm):
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
}
class MaintenancePartForm(forms.ModelForm):
class Meta:
model = MaintenancePart
fields = ['part_name', 'article_number', 'quantity']
widgets = {
'part_name': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Например: Масло 5W-30'}),
'article_number': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Артикул'}),
'quantity': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Кол-во'}),
}
class BreakdownForm(forms.ModelForm):
class Meta:
model = Breakdown
@ -181,6 +191,11 @@ class MaintenanceDetailView(DetailView):
template_name = 'core/maintenance_detail.html'
context_object_name = 'maintenance'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['part_form'] = MaintenancePartForm()
return context
class MaintenanceCreateView(CreateView):
model = Maintenance
template_name = 'core/maintenance_form.html'
@ -248,9 +263,27 @@ class MaintenanceCompleteView(View):
return redirect('maintenance_detail', pk=pk)
class MaintenancePartAddView(View):
def post(self, request, pk):
maintenance = get_object_or_404(Maintenance, pk=pk)
form = MaintenancePartForm(request.POST)
if form.is_valid():
part = form.save(commit=False)
part.maintenance = maintenance
part.save()
return redirect('maintenance_detail', pk=pk)
class MaintenancePartDeleteView(View):
def post(self, request, pk):
part = get_object_or_404(MaintenancePart, pk=pk)
maintenance_pk = part.maintenance.pk
part.delete()
return redirect('maintenance_detail', pk=maintenance_pk)
class MaintenancePDFView(View):
def get(self, request, pk):
maintenance = get_object_or_404(Maintenance, pk=pk)
unit = maintenance.fleet_unit
buffer = BytesIO()
p = canvas.Canvas(buffer, pagesize=A4)
@ -262,19 +295,103 @@ class MaintenancePDFView(View):
except:
pass # Fallback to default if font not found
p.drawString(100, 800, f"Акт технического обслуживания - {maintenance.m_type}")
p.drawString(100, 780, f"Техника: {maintenance.fleet_unit.name}")
p.drawString(100, 760, f"Дата: {maintenance.actual_date}")
p.drawString(100, 740, f"Наработка: {maintenance.actual_runtime}")
# Header
p.setFont('LiberationSans', 16)
p.drawString(100, 800, f"Акт технического обслуживания: {maintenance.m_type}")
y = 700
p.drawString(100, y, "Чек-лист:")
p.setFont('LiberationSans', 10)
p.drawString(100, 780, f"Статус ТО: {maintenance.get_status_display()}")
p.setFont('LiberationSans', 12)
p.setStrokeColorRGB(0.7, 0.7, 0.7)
p.line(100, 770, 500, 770)
# Unit Info (The Header requested by user)
y = 750
p.setFont('LiberationSans', 11)
p.drawString(100, y, f"ТЕХНИКА: {unit.name}")
y -= 15
p.drawString(100, y, f"Модель: {unit.model_name}")
y -= 15
p.drawString(100, y, f"VIN / Серийный номер: {unit.vin}")
y -= 15
p.drawString(100, y, f"Госномер: {unit.plate_number or '-'}")
y -= 15
p.drawString(100, y, f"Год выпуска: {unit.year}")
y -= 25
p.line(100, y, 500, y)
y -= 15
# Execution info
p.drawString(100, y, f"Дата проведения: {maintenance.actual_date or '-'}")
y -= 15
p.drawString(100, y, f"Наработка (моточасы/км): {maintenance.actual_runtime or '-'}")
y -= 15
p.drawString(100, y, f"Исполнитель: {maintenance.mechanic.get_full_name() or maintenance.mechanic.username if maintenance.mechanic else '-'}")
y -= 30
p.setFont('LiberationSans', 14)
p.drawString(100, y, "Чек-лист операций:")
y -= 20
p.setFont('LiberationSans', 10)
for item in maintenance.checklist:
status = "[x]" if item['done'] else "[ ]"
p.drawString(120, y, f"{status} {item['task']}")
y -= 20
y -= 15
if y < 100:
p.showPage()
y = 800
p.setFont('LiberationSans', 10)
# Used Parts and Fluids (The specific list requested by user)
y -= 20
if y < 150:
p.showPage()
y = 800
p.setFont('LiberationSans', 14)
p.drawString(100, y, "Использованные запчасти и жидкости:")
y -= 20
# Table Header for parts
p.setFont('LiberationSans', 10)
p.drawString(100, y, "Наименование")
p.drawString(300, y, "Артикул")
p.drawString(450, y, "Кол-во")
y -= 5
p.line(100, y, 500, y)
y -= 15
parts = maintenance.used_parts.all()
if parts:
for part in parts:
p.drawString(100, y, f"{part.part_name}")
p.drawString(300, y, f"{part.article_number or '-'}")
p.drawString(450, y, f"{part.quantity}")
y -= 15
if y < 100:
p.showPage()
y = 800
else:
p.drawString(100, y, "Нет записей")
y -= 15
y -= 20
if maintenance.notes:
p.setFont('LiberationSans', 11)
p.drawString(100, y, "Примечания:")
y -= 15
p.setFont('LiberationSans', 9)
# Simple text wrapping for notes
lines = maintenance.notes.split('\n')
for line in lines:
p.drawString(120, y, line[:80])
y -= 12
if y < 100:
p.showPage()
y = 800
p.showPage()
p.save()

View File

@ -1,116 +1,191 @@
:root {
--bg-slate-900: #1e293b;
--text-blue-500: #3b82f6;
--primary: #3b82f6;
--secondary: #64748b;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--info: #06b6d4;
--apple-bg: #f5f5f7;
--apple-card-bg: #ffffff;
--apple-blue: #0071e3;
--apple-text: #1d1d1f;
--apple-text-secondary: #86868b;
--apple-border: #d2d2d7;
--primary: #0071e3;
--secondary: #86868b;
--success: #28cd41;
--danger: #ff3b30;
--warning: #ff9f0a;
--info: #55bef0;
}
body {
font-family: 'Inter', sans-serif;
color: #334155;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
background-color: var(--apple-bg);
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
font-weight: 700;
font-weight: 600;
letter-spacing: -0.022em;
}
.bg-slate-900 {
background-color: var(--bg-slate-900) !important;
/* Glassmorphism Navbar */
.navbar-apple {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.text-blue-500 {
color: var(--text-blue-500) !important;
.navbar-apple .nav-link {
color: var(--apple-text) !important;
font-size: 0.9rem;
font-weight: 400;
}
.tracking-wider {
letter-spacing: 0.05em;
.navbar-apple .nav-link:hover {
color: var(--apple-blue) !important;
}
/* Card Styling */
.navbar-apple .navbar-brand {
color: var(--apple-text) !important;
font-weight: 600;
}
/* Apple Cards */
.card {
background: var(--apple-card-bg);
border: none;
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-radius: 20px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
overflow: hidden;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
transform: scale(1.01);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}
/* Stat Cards */
.stat-card {
padding: 1.5rem;
display: flex;
align-items: center;
.card-header {
background: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
padding: 1.25rem 1.5rem;
}
.stat-icon {
width: 48px;
height: 48px;
/* Buttons */
.btn {
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-right: 1rem;
padding: 0.6rem 1.2rem;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-primary {
background-color: var(--apple-blue);
border-color: var(--apple-blue);
}
.btn-primary:hover {
background-color: #0077ed;
border-color: #0077ed;
}
.btn-outline-primary {
color: var(--apple-blue);
border-color: var(--apple-blue);
}
/* Form Controls */
.form-control, .form-select {
background-color: #fbfbfd;
border: 1px solid var(--apple-border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 1rem;
color: var(--apple-text);
}
.form-control:focus {
background-color: #ffffff;
border-color: var(--apple-blue);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
}
/* Badges */
.badge {
padding: 0.5em 0.8em;
font-weight: 500;
border-radius: 6px;
border-radius: 8px;
padding: 0.4em 0.8em;
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.02em;
}
/* Hero Section */
.hero-gradient {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
color: white;
padding: 3rem 0;
border-radius: 16px;
/* Stat Cards Customization */
.stat-card {
border-radius: 20px;
}
.stat-icon {
background: rgba(0, 113, 227, 0.1);
color: var(--apple-blue);
border-radius: 14px;
}
/* Hero section */
.hero-section {
background: #ffffff;
border-radius: 24px;
padding: 4rem 2rem;
margin-bottom: 2rem;
text-align: center;
}
/* Form Styling */
.form-control, .form-select {
border-radius: 8px;
padding: 0.625rem 0.75rem;
border: 1px solid #e2e8f0;
.text-secondary-apple {
color: var(--apple-text-secondary) !important;
}
.form-control:focus, .form-select:focus {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
border-color: #3b82f6;
/* Table styling */
.table {
--bs-table-bg: transparent;
}
.btn {
border-radius: 8px;
padding: 0.625rem 1.25rem;
font-weight: 500;
.table thead th {
font-weight: 600;
color: var(--apple-text-secondary);
text-transform: uppercase;
font-size: 0.75rem;
border-bottom: 1px solid var(--apple-border);
}
.btn-primary {
background-color: var(--primary);
border-color: var(--primary);
/* List group */
.list-group-item {
border-color: rgba(0, 0, 0, 0.05);
padding: 1rem 1.5rem;
}
.btn-primary:hover {
background-color: #2563eb;
border-color: #2563eb;
.list-group-item:first-child {
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.stat-card {
padding: 1rem;
}
.hero-gradient {
padding: 2rem 1rem;
}
}
.list-group-item:last-child {
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
/* Custom Scrollbar for modern look */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d2d2d7;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #86868b;
}

View File

@ -1,116 +1,191 @@
:root {
--bg-slate-900: #1e293b;
--text-blue-500: #3b82f6;
--primary: #3b82f6;
--secondary: #64748b;
--success: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--info: #06b6d4;
--apple-bg: #f5f5f7;
--apple-card-bg: #ffffff;
--apple-blue: #0071e3;
--apple-text: #1d1d1f;
--apple-text-secondary: #86868b;
--apple-border: #d2d2d7;
--primary: #0071e3;
--secondary: #86868b;
--success: #28cd41;
--danger: #ff3b30;
--warning: #ff9f0a;
--info: #55bef0;
}
body {
font-family: 'Inter', sans-serif;
color: #334155;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
background-color: var(--apple-bg);
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
font-weight: 700;
font-weight: 600;
letter-spacing: -0.022em;
}
.bg-slate-900 {
background-color: var(--bg-slate-900) !important;
/* Glassmorphism Navbar */
.navbar-apple {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.text-blue-500 {
color: var(--text-blue-500) !important;
.navbar-apple .nav-link {
color: var(--apple-text) !important;
font-size: 0.9rem;
font-weight: 400;
}
.tracking-wider {
letter-spacing: 0.05em;
.navbar-apple .nav-link:hover {
color: var(--apple-blue) !important;
}
/* Card Styling */
.navbar-apple .navbar-brand {
color: var(--apple-text) !important;
font-weight: 600;
}
/* Apple Cards */
.card {
background: var(--apple-card-bg);
border: none;
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-radius: 20px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
overflow: hidden;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
transform: scale(1.01);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
}
/* Stat Cards */
.stat-card {
padding: 1.5rem;
display: flex;
align-items: center;
.card-header {
background: transparent;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
padding: 1.25rem 1.5rem;
}
.stat-icon {
width: 48px;
height: 48px;
/* Buttons */
.btn {
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-right: 1rem;
padding: 0.6rem 1.2rem;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-primary {
background-color: var(--apple-blue);
border-color: var(--apple-blue);
}
.btn-primary:hover {
background-color: #0077ed;
border-color: #0077ed;
}
.btn-outline-primary {
color: var(--apple-blue);
border-color: var(--apple-blue);
}
/* Form Controls */
.form-control, .form-select {
background-color: #fbfbfd;
border: 1px solid var(--apple-border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 1rem;
color: var(--apple-text);
}
.form-control:focus {
background-color: #ffffff;
border-color: var(--apple-blue);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
}
/* Badges */
.badge {
padding: 0.5em 0.8em;
font-weight: 500;
border-radius: 6px;
border-radius: 8px;
padding: 0.4em 0.8em;
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.02em;
}
/* Hero Section */
.hero-gradient {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
color: white;
padding: 3rem 0;
border-radius: 16px;
/* Stat Cards Customization */
.stat-card {
border-radius: 20px;
}
.stat-icon {
background: rgba(0, 113, 227, 0.1);
color: var(--apple-blue);
border-radius: 14px;
}
/* Hero section */
.hero-section {
background: #ffffff;
border-radius: 24px;
padding: 4rem 2rem;
margin-bottom: 2rem;
text-align: center;
}
/* Form Styling */
.form-control, .form-select {
border-radius: 8px;
padding: 0.625rem 0.75rem;
border: 1px solid #e2e8f0;
.text-secondary-apple {
color: var(--apple-text-secondary) !important;
}
.form-control:focus, .form-select:focus {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
border-color: #3b82f6;
/* Table styling */
.table {
--bs-table-bg: transparent;
}
.btn {
border-radius: 8px;
padding: 0.625rem 1.25rem;
font-weight: 500;
.table thead th {
font-weight: 600;
color: var(--apple-text-secondary);
text-transform: uppercase;
font-size: 0.75rem;
border-bottom: 1px solid var(--apple-border);
}
.btn-primary {
background-color: var(--primary);
border-color: var(--primary);
/* List group */
.list-group-item {
border-color: rgba(0, 0, 0, 0.05);
padding: 1rem 1.5rem;
}
.btn-primary:hover {
background-color: #2563eb;
border-color: #2563eb;
.list-group-item:first-child {
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.stat-card {
padding: 1rem;
}
.hero-gradient {
padding: 2rem 1rem;
}
}
.list-group-item:last-child {
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
/* Custom Scrollbar for modern look */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d2d2d7;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #86868b;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB