From df45ad71cc647adfac3f750fe762d3a85128a245 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 27 Jan 2026 18:19:22 +0000 Subject: [PATCH] Ver.01 --- core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 2559 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 11276 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 1032 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 6549 bytes core/admin.py | 30 +- core/migrations/0001_initial.py | 119 +++++++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 8653 bytes core/models.py | 157 +++++++++- core/templates/base.html | 95 ++++-- core/templates/core/fleet_detail.html | 186 +++++++++++ core/templates/core/fleet_form.html | 115 +++++++ core/templates/core/fleet_list.html | 119 +++++++ core/templates/core/index.html | 291 +++++++++--------- core/urls.py | 11 +- core/views.py | 92 ++++-- static/css/custom.css | 118 ++++++- 16 files changed, 1142 insertions(+), 191 deletions(-) create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/templates/core/fleet_detail.html create mode 100644 core/templates/core/fleet_form.html create mode 100644 core/templates/core/fleet_list.html diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392d6714413db63120e4233d2e96cbadb5de..edb7428cfb21704c6fade5257dfff5c3fb80ed3a 100644 GIT binary patch literal 2559 zcmbVN%Wm676rCX{ntEDJ?51{Y!!7a>ibjhTUBv~O#x07(LXjd}a6{0VvBQK9Wk@#F zPK$hicliSsMt&v+yf9`JpsVa;*sH91FKIFi#R6_f@n|@6d3f%bxx>HP?FNCvCSzwy zBjg{v$qxOZakj1y@`^CRs7pL*Qc9S@R9Ep-Q}r}c^J-?z(@ou*Gv_?RG$>IBr%&q{ z5myf=A+O;|4~6_fnD&ye8bl@U$NmEqV&F=T1NeKL-4L@`V$>=abzq#kG-Ip8SgT+( zfU$mQ#&(HOuV8EdWAoCCof6|*1!D^s+m~jXFEJVwj2&Q{zcgc)b$=(lg?BhtJt}K9 z^Bn&j&UH_h#!q&{_X07N^`BkNqsP7z$>z50_!0MQf5>J1p5XQ~7QFCf^EX>WkNESL zhmkby2gBIoel*M%0W`8Dban-9UJ*`A%802jYN|{D8LDh&Z~PH;fFRAcJw7~2mT;F%_dp`VagCohbPw8WP{B%9$l zjJRj{0ZY@68fJ7zlbirjctriWT)FrQgIs?nap)!ULx+}%6xC!O*g(D#P<-4yJ>g^ zI8`a7CuHNz$G;Qu$D9B2drRjhOE1^=jU&2zOqZt$rE60%3;SEgi`P$a;2hypCCW-3 zuM?%4u+)zz^sY=5m3}uRv#|g8cUYf5HeKR&*8 z>l6ph5s+mqk2f-wT#G{2k);e5T3`TK)<0;$(43{9rA&~J=2||mWLgR=1$Q^m-7@6v PZlb%JMRyCK$=>uY#`+$I delta 151 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 EUx!o^KL7v# delta 144 zcmeB)xX75WoR^o20SKo5$j`I{(vLwL7+``jJ_`XE(-~42QW$d>av7r-85vTTf*CZK zUxE~9GTvg#%}+_qDfZK3y2Y82m6(^Fua}Zk#0-?2ypG9S>=%a(P(f)d0X Vi)9%muh){~zQ7;@L`7^sApljO9}@ro diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659f6c6e0ae848e54157af197c543a09315f..8e219cc5b73ea8888ed3f8cce0dd2b3cc9dd1bbb 100644 GIT binary patch literal 1032 zcmb7?&1(}u7{+Il$>(;B7&S!}Bv1l@lC97LYD*{>g@6dbR3RS1ni<<|H@jg!Y#TiE zSj0nbdhDqxdhp=EWB-KRFp#iULFg^eTTjmHb~Pop(;()67SJ0OC~8P2M zPe8Hn@uMhJ=o=KDlc1bURlu23LC;mb=0mR(pT zusniEI%4*qOtOVxwlv6=-0T9$F8p}sz?utd1lIf^PU_yL$3%QE6qg3#k}EC~arwC9 zz=jJO1U3SUcwm#<{4lpT$Su0L3dvP|y>ejPg>?e!=a~D4cSyc6%vT5bs+(UW`PE~( WRMUk`0-Iw#o{8-7>@n`rt^NhB9Q(}x delta 252 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 Q?Lh0dV)D^rRD?JG4{;H=qyPW_ literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 71a8362..35097bc 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,158 @@ from django.db import models +from django.urls import reverse -# Create your models here. +class Category(models.Model): + name = models.CharField(max_length=100, verbose_name="Наименование") + + class Meta: + verbose_name = "Категория" + verbose_name_plural = "Категории" + + def __str__(self): + return self.name + +class FleetUnit(models.Model): + STATUS_CHOICES = [ + ('active', 'В работе'), + ('idle', 'Простаивает'), + ('broken', 'Сломана'), + ('repair', 'В ремонте'), + ('waiting_parts', 'Ждёт деталь'), + ] + + name = models.CharField(max_length=255, verbose_name="Наименование") + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Категория") + model_name = models.CharField(max_length=255, verbose_name="Модель") + vin = models.CharField(max_length=100, unique=True, verbose_name="VIN / Серийный номер") + plate_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="Госномер") + year = models.PositiveIntegerField(verbose_name="Год выпуска") + photo = models.ImageField(upload_to='fleet_photos/', blank=True, null=True, verbose_name="Фото") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', verbose_name="Статус") + commissioning_date = models.DateField(verbose_name="Дата ввода в эксплуатацию") + notes = models.TextField(blank=True, null=True, verbose_name="Примечания") + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Техника" + verbose_name_plural = "Техника" + ordering = ['-created_at'] + + def __str__(self): + return f"{self.name} ({self.plate_number or self.vin})" + + def get_absolute_url(self): + return reverse('fleet_detail', kwargs={'pk': self.pk}) + + def get_status_color(self): + colors = { + 'active': 'success', + 'idle': 'secondary', + 'broken': 'danger', + 'repair': 'warning', + 'waiting_parts': 'info', + } + return colors.get(self.status, 'primary') + +class Maintenance(models.Model): + TYPE_CHOICES = [ + ('TO-250', 'ТО-250'), + ('TO-500', 'ТО-500'), + ('seasonal', 'Сезонное'), + ('individual', 'Индивидуальное'), + ] + STATUS_CHOICES = [ + ('planned', 'Планируется'), + ('in_progress', 'В процессе'), + ('completed', 'Выполнено'), + ] + + fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='maintenances', verbose_name="Техника") + m_type = models.CharField(max_length=50, choices=TYPE_CHOICES, verbose_name="Тип ТО") + planned_date = models.DateField(verbose_name="Плановая дата") + planned_runtime = models.PositiveIntegerField(verbose_name="Плановый пробег / моточасы") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planned', verbose_name="Статус") + + actual_date = models.DateField(null=True, blank=True, verbose_name="Фактическая дата") + actual_runtime = models.PositiveIntegerField(null=True, blank=True, verbose_name="Фактический пробег / моточасы") + + notes = models.TextField(blank=True, null=True, verbose_name="Примечания") + + class Meta: + verbose_name = "ТО" + verbose_name_plural = "ТО" + + def __str__(self): + return f"{self.m_type} - {self.fleet_unit.name} ({self.planned_date})" + +class Breakdown(models.Model): + STATUS_CHOICES = [ + ('reported', 'Заявлено'), + ('repaired', 'Отремонтировано'), + ('need_part', 'Нужна деталь'), + ] + + fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='breakdowns', verbose_name="Техника") + date = models.DateField(auto_now_add=True, verbose_name="Дата") + system_node = models.CharField(max_length=255, verbose_name="Узел / система") + description = models.TextField(verbose_name="Описание") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='reported', verbose_name="Статус") + repair_date = models.DateField(null=True, blank=True, verbose_name="Дата ремонта") + cost = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Стоимость") + + class Meta: + verbose_name = "Поломка" + verbose_name_plural = "Поломки" + + def __str__(self): + return f"Поломка: {self.fleet_unit.name} - {self.system_node}" + +class PartRequest(models.Model): + STATUS_CHOICES = [ + ('draft', 'Черновик'), + ('sent', 'Отправлено'), + ('ordered', 'Заказано'), + ('delivered', 'Доставлено'), + ] + + fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='part_requests', 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.PositiveIntegerField(default=1, verbose_name="Количество") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft', verbose_name="Статус") + photo = models.ImageField(upload_to='part_photos/', blank=True, null=True, verbose_name="Фото") + notes = models.TextField(blank=True, null=True, verbose_name="Примечание") + + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + verbose_name = "Заявка на запчасть" + verbose_name_plural = "Заявки на запчасти" + + def __str__(self): + return f"{self.part_name} for {self.fleet_unit.name}" + +class Document(models.Model): + TYPE_CHOICES = [ + ('passport', 'Паспорт'), + ('maintenance_act', 'Акт ТО'), + ('photo', 'Фото'), + ('invoice', 'Счет'), + ] + + doc_type = models.CharField(max_length=50, choices=TYPE_CHOICES, verbose_name="Тип документа") + fleet_unit = models.ForeignKey(FleetUnit, on_delete=models.CASCADE, related_name='documents', null=True, blank=True) + maintenance = models.ForeignKey(Maintenance, on_delete=models.CASCADE, related_name='documents', null=True, blank=True) + breakdown = models.ForeignKey(Breakdown, on_delete=models.CASCADE, related_name='documents', null=True, blank=True) + part_request = models.ForeignKey(PartRequest, on_delete=models.CASCADE, related_name='documents', null=True, blank=True) + + file = models.FileField(upload_to='documents/', verbose_name="Файл") + uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата добавления") + + class Meta: + verbose_name = "Документ" + verbose_name_plural = "Документы" + + def __str__(self): + return f"{self.get_doc_type_display()} - {self.uploaded_at}" diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..c5be69c 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,80 @@ +{% load static %} - - + - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Fleet Manager{% endblock %} + + + + + + + + + + + + + + + {% block extra_css %}{% endblock %} + + + - - {% block content %}{% endblock %} +
+
+ {% block content %}{% endblock %} +
+
+ +
+
+

© {% now "Y" %} Fleet Manager. Все права защищены.

+
+
+ + + + {% block extra_js %}{% endblock %} - - + \ No newline at end of file diff --git a/core/templates/core/fleet_detail.html b/core/templates/core/fleet_detail.html new file mode 100644 index 0000000..13657bc --- /dev/null +++ b/core/templates/core/fleet_detail.html @@ -0,0 +1,186 @@ +{% extends 'base.html' %} + +{% block title %}{{ unit.name }} | Fleet Manager{% endblock %} + +{% block content %} +
+ +
+

{{ unit.name }}

+
+ + Редактировать + + +
+
+
+ +
+
+ +
+
+
+ {% if unit.photo %} + {{ unit.name }} + {% else %} +
+ +
+ {% endif %} +
+
+
+
+ + {{ unit.get_status_display }} + +
ID: {{ unit.pk }}
+
+ +
+
+ + {{ unit.category|default:"Не указана" }} +
+
+ + {{ unit.model_name }} +
+
+ + {{ unit.plate_number|default:"-" }} +
+
+ + {{ unit.year }} +
+
+ + {{ unit.vin }} +
+
+ + {{ unit.commissioning_date|date:"d.m.Y" }} +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+ + + + + + + + + + + {% for m in maintenances %} + + + + + + + {% empty %} + + {% endfor %} + +
ТипДатаПробег/мчСтатус
{{ m.m_type }}{{ m.planned_date|date:"d.m.Y" }}{{ m.planned_runtime }}{{ m.get_status_display }}
История ТО пуста
+
+
+
+
Журнал поломок пуст
+
+
+
Заявок на запчасти нет
+
+
+
Документы не загружены
+
+
+
+
+
+ +
+ +
+
+
Действия
+
+ + + +
+
+
+ + +
+
+
QR-код техники
+
+ +
+ +
+
+

Отсканируйте для быстрого доступа с мобильного телефона

+ +
+
+ + +
+
+
Примечания
+

{{ unit.notes|default:"Примечания отсутствуют" }}

+
+
+
+
+{% endblock %} diff --git a/core/templates/core/fleet_form.html b/core/templates/core/fleet_form.html new file mode 100644 index 0000000..d9bc7eb --- /dev/null +++ b/core/templates/core/fleet_form.html @@ -0,0 +1,115 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %} + {% if object %}Редактирование {{ object.name }}{% else %}Добавление техники{% endif %} | Fleet Manager +{% endblock %} + +{% block content %} +
+
+
+ +

{% if object %}Редактирование {{ object.name }}{% else %}Новая единица техники{% endif %}

+
+ +
+
+
+ {% csrf_token %} + +
+
+ + {{ form.name }} + {% if form.name.errors %}
{{ form.name.errors }}
{% endif %} +
+ +
+ + {{ form.category }} + {% if form.category.errors %}
{{ form.category.errors }}
{% endif %} +
+ +
+ + {{ form.model_name }} + {% if form.model_name.errors %}
{{ form.model_name.errors }}
{% endif %} +
+ +
+ + {{ form.vin }} + {% if form.vin.errors %}
{{ form.vin.errors }}
{% endif %} +
+ +
+ + {{ form.plate_number }} + {% if form.plate_number.errors %}
{{ form.plate_number.errors }}
{% endif %} +
+ +
+ + {{ form.year }} + {% if form.year.errors %}
{{ form.year.errors }}
{% endif %} +
+ +
+ + {{ form.status }} + {% if form.status.errors %}
{{ form.status.errors }}
{% endif %} +
+ +
+ + {{ form.commissioning_date }} +
ГГГГ-ММ-ДД
+ {% if form.commissioning_date.errors %}
{{ form.commissioning_date.errors }}
{% endif %} +
+ +
+ + {{ form.photo }} + {% if form.photo.errors %}
{{ form.photo.errors }}
{% endif %} +
+ +
+ + {{ form.notes }} + {% if form.notes.errors %}
{{ form.notes.errors }}
{% endif %} +
+
+ +
+ + Отмена +
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/fleet_list.html b/core/templates/core/fleet_list.html new file mode 100644 index 0000000..5f7a38a --- /dev/null +++ b/core/templates/core/fleet_list.html @@ -0,0 +1,119 @@ +{% extends 'base.html' %} + +{% block title %}Список техники | Fleet Manager{% endblock %} + +{% block content %} +
+
+

Парк техники

+ +
+ + Добавить технику + +
+ + +
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+ +
+ {% for unit in units %} +
+
+
+
+ {% if unit.photo %} + {{ unit.name }} + {% else %} +
+ +
+ {% endif %} +
+
+
+
+ + {{ unit.get_status_display }} + + #{{ unit.pk }} +
+
+ {{ unit.name }} +
+

+ {{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }} +

+
+
+ {{ unit.year }} +
+ Подробнее +
+
+
+
+
+
+ {% empty %} +
+
+ +
+

Техника не найдена

+

Попробуйте изменить параметры поиска или добавить новую единицу.

+ Добавить технику +
+ {% endfor %} +
+ + +{% if is_paginated %} + +{% endif %} +{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..5018798 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,154 @@ -{% extends "base.html" %} +{% extends 'base.html' %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}Дашборд | Fleet Manager{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
+

Управление парком техники

+

Централизованная система контроля, обслуживания и учета вашей техники.

+ +
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file + + +
+
+
+
+ +
+
+
Всего единиц
+
{{ total_units }}
+
+
+
+
+
+
+ +
+
+
В работе
+
{{ active_units }}
+
+
+
+
+
+
+ +
+
+
Сломано
+
{{ broken_units }}
+
+
+
+
+
+
+ +
+
+
В ремонте
+
{{ repair_units }}
+
+
+
+
+ +
+
+
+
+
Предстоящие ТО
+ Все ТО +
+
+
+ + + + + + + + + + + {% for m in recent_maintenances %} + + + + + + + {% empty %} + + + + {% endfor %} + +
ТехникаТипДатаСтатус
{{ m.fleet_unit.name }}{{ m.m_type }}{{ m.planned_date|date:"d.m.Y" }} + + {{ m.get_status_display }} + +
Нет запланированных ТО
+
+
+
+
+
+
+
+
Последние поломки
+ Журнал +
+
+
+ + + + + + + + + + + {% for b in recent_breakdowns %} + + + + + + + {% empty %} + + + + {% endfor %} + +
ТехникаУзелДатаСтатус
{{ b.fleet_unit.name }}{{ b.system_node }}{{ b.date|date:"d.m.Y" }} + + {{ b.get_status_display }} + +
Поломок не зафиксировано
+
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..8a75e00 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,10 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), -] + path('', views.IndexView.as_view(), name='index'), + path('fleet/', views.FleetListView.as_view(), name='fleet_list'), + path('fleet//', views.FleetDetailView.as_view(), name='fleet_detail'), + path('fleet/add/', views.FleetCreateView.as_view(), name='fleet_add'), + path('fleet//edit/', views.FleetUpdateView.as_view(), name='fleet_edit'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..c649a25 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,75 @@ -import os -import platform - -from django import get_version as django_version from django.shortcuts import render -from django.utils import timezone +from django.views.generic import ListView, DetailView, CreateView, UpdateView, TemplateView +from django.urls import reverse_lazy +from django import forms +from django.db.models import Count +from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category +class FleetUnitForm(forms.ModelForm): + class Meta: + model = FleetUnit + fields = ['name', 'category', 'model_name', 'vin', 'plate_number', 'year', 'photo', 'status', 'commissioning_date', 'notes'] + widgets = { + 'commissioning_date': forms.DateInput(attrs={'type': 'date'}), + 'notes': forms.Textarea(attrs={'rows': 3}), + } -def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() +class IndexView(TemplateView): + template_name = 'core/index.html' - context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), - } - return render(request, "core/index.html", context) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['total_units'] = FleetUnit.objects.count() + context['active_units'] = FleetUnit.objects.filter(status='active').count() + context['broken_units'] = FleetUnit.objects.filter(status='broken').count() + context['repair_units'] = FleetUnit.objects.filter(status='repair').count() + + # Stats for charts or badges + context['status_counts'] = FleetUnit.objects.values('status').annotate(total=Count('status')) + + context['recent_maintenances'] = Maintenance.objects.all().order_by('-planned_date')[:5] + context['recent_breakdowns'] = Breakdown.objects.all().order_by('-date')[:5] + + return context + +class FleetListView(ListView): + model = FleetUnit + template_name = 'core/fleet_list.html' + context_object_name = 'units' + paginate_by = 12 + + def get_queryset(self): + queryset = super().get_queryset() + status = self.request.GET.get('status') + if status: + queryset = queryset.filter(status=status) + search = self.request.GET.get('search') + if search: + queryset = queryset.filter(name__icontains=search) | queryset.filter(plate_number__icontains=search) | queryset.filter(vin__icontains=search) + return queryset + +class FleetDetailView(DetailView): + model = FleetUnit + template_name = 'core/fleet_detail.html' + context_object_name = 'unit' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['maintenances'] = self.object.maintenances.all() + context['breakdowns'] = self.object.breakdowns.all() + context['part_requests'] = self.object.part_requests.all() + return context + +class FleetCreateView(CreateView): + model = FleetUnit + template_name = 'core/fleet_form.html' + form_class = FleetUnitForm + success_url = reverse_lazy('fleet_list') + +class FleetUpdateView(UpdateView): + model = FleetUnit + template_name = 'core/fleet_form.html' + form_class = FleetUnitForm + + def get_success_url(self): + return reverse_lazy('fleet_detail', kwargs={'pk': self.object.pk}) \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..424cf4a 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,116 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +:root { + --bg-slate-900: #1e293b; + --text-blue-500: #3b82f6; + --primary: #3b82f6; + --secondary: #64748b; + --success: #10b981; + --danger: #ef4444; + --warning: #f59e0b; + --info: #06b6d4; } + +body { + font-family: 'Inter', sans-serif; + color: #334155; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Montserrat', sans-serif; + font-weight: 700; +} + +.bg-slate-900 { + background-color: var(--bg-slate-900) !important; +} + +.text-blue-500 { + color: var(--text-blue-500) !important; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +/* Card Styling */ +.card { + border: none; + border-radius: 12px; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.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; +} + +/* Stat Cards */ +.stat-card { + padding: 1.5rem; + display: flex; + align-items: center; +} + +.stat-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-right: 1rem; +} + +/* Badges */ +.badge { + padding: 0.5em 0.8em; + font-weight: 500; + border-radius: 6px; +} + +/* Hero Section */ +.hero-gradient { + background: linear-gradient(135deg, #1e293b 0%, #334155 100%); + color: white; + padding: 3rem 0; + border-radius: 16px; + margin-bottom: 2rem; +} + +/* Form Styling */ +.form-control, .form-select { + border-radius: 8px; + padding: 0.625rem 0.75rem; + border: 1px solid #e2e8f0; +} + +.form-control:focus, .form-select:focus { + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + border-color: #3b82f6; +} + +.btn { + border-radius: 8px; + padding: 0.625rem 1.25rem; + font-weight: 500; +} + +.btn-primary { + background-color: var(--primary); + border-color: var(--primary); +} + +.btn-primary:hover { + background-color: #2563eb; + border-color: #2563eb; +} + +/* Mobile adjustments */ +@media (max-width: 768px) { + .stat-card { + padding: 1rem; + } + .hero-gradient { + padding: 2rem 1rem; + } +} \ No newline at end of file