├── clientes ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── models.py ├── views.py └── templates │ └── clientes.html ├── mecajato ├── __init__.py ├── urls.py ├── asgi.py ├── wsgi.py └── settings.py ├── servicos ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_alter_servico_protocole.py │ ├── 0003_servico_identificador.py │ ├── 0004_servicoadicional_servico_servicos_adicionais.py │ └── 0001_initial.py ├── tests.py ├── apps.py ├── admin.py ├── choices.py ├── urls.py ├── forms.py ├── templates │ ├── novo_servico.html │ ├── listar_servico.html │ └── servico.html ├── models.py └── views.py ├── os.pdf ├── requirements.txt ├── templates ├── static │ ├── servicos │ │ └── css │ │ │ └── listar_servico.css │ ├── general │ │ ├── css │ │ │ ├── general.css │ │ │ └── base.css │ │ └── js │ │ │ └── base.js │ └── clientes │ │ ├── css │ │ └── clientes.css │ │ └── js │ │ └── clientes.js └── base.html ├── manage.py └── .gitignore /clientes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mecajato/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /servicos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /clientes/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /servicos/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /os.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pythonando/mecajato/HEAD/os.pdf -------------------------------------------------------------------------------- /clientes/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /servicos/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.5.2 2 | backports.zoneinfo==0.2.1 3 | Django==4.1.1 4 | sqlparse==0.4.2 5 | fpdf==1.7.2 6 | -------------------------------------------------------------------------------- /templates/static/servicos/css/listar_servico.css: -------------------------------------------------------------------------------- 1 | .distanciamento{ 2 | padding-left: 100px; 3 | padding-right: 100px; 4 | } -------------------------------------------------------------------------------- /clientes/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Cliente, Carro 3 | 4 | admin.site.register(Cliente) 5 | admin.site.register(Carro) -------------------------------------------------------------------------------- /clientes/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ClientesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'clientes' 7 | -------------------------------------------------------------------------------- /servicos/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ServicosConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'servicos' 7 | -------------------------------------------------------------------------------- /templates/static/general/css/general.css: -------------------------------------------------------------------------------- 1 | :root{ 2 | 3 | --main-color: #ac4147; 4 | --dark-color: #411f2d; 5 | --light-color: #ffc27f; 6 | --contrast-color: #f88863; 7 | --differential-color: #ffe29a; 8 | 9 | } -------------------------------------------------------------------------------- /servicos/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import CategoriaManutencao, Servico, ServicoAdicional 3 | 4 | admin.site.register(CategoriaManutencao) 5 | admin.site.register(Servico) 6 | admin.site.register(ServicoAdicional) -------------------------------------------------------------------------------- /servicos/choices.py: -------------------------------------------------------------------------------- 1 | from django.db.models import TextChoices 2 | 3 | class ChoicesCategoriaManutencao(TextChoices): 4 | TROCAR_VALVULA_MOTOR = "TVM", "Trocar válcula do motor" 5 | TROCAR_OLEO = "TO", "Troca de óleo" 6 | BALANCEAMENTO = "B", "Balanceamento" -------------------------------------------------------------------------------- /mecajato/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('admin/', admin.site.urls), 6 | path('clientes/', include('clientes.urls')), 7 | path('servicos/', include('servicos.urls')), 8 | ] 9 | -------------------------------------------------------------------------------- /clientes/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.clientes, name="clientes"), 6 | path('atualiza_cliente/', views.att_cliente, name="atualiza_cliente"), 7 | path('excluir_carro/', views.excluir_carro, name="excluir_carro"), 8 | path('update_carro/', views.update_carro, name="update_carro"), 9 | path('update_cliente/', views.update_cliente, name="update_cliente") 10 | ] -------------------------------------------------------------------------------- /mecajato/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for mecajato project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mecajato.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /mecajato/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mecajato project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mecajato.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /servicos/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('novo_servico/', views.novo_servico, name="novo_servico"), 6 | path('listar_servico/', views.listar_servico, name="listar_servico"), 7 | path('servico//', views.servico, name="servico"), 8 | path('gerar_os/', views.gerar_os, name="gerar_os"), 9 | path('servico_adicional/', views.servico_adicional, name="servico_adicional") 10 | ] -------------------------------------------------------------------------------- /servicos/migrations/0002_alter_servico_protocole.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.1 on 2022-10-06 21:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('servicos', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='servico', 15 | name='protocole', 16 | field=models.CharField(blank=True, max_length=52, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /servicos/migrations/0003_servico_identificador.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.1 on 2023-03-02 21:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('servicos', '0002_alter_servico_protocole'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='servico', 15 | name='identificador', 16 | field=models.CharField(blank=True, max_length=24, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /templates/static/general/js/base.js: -------------------------------------------------------------------------------- 1 | let sidebar = document.querySelector(".sidebar"); 2 | let closeBtn = document.querySelector("#btn"); 3 | let searchBtn = document.querySelector(".bx-search"); 4 | 5 | closeBtn.addEventListener("click", ()=>{ 6 | sidebar.classList.toggle("open"); 7 | menuBtnChange(); 8 | }); 9 | 10 | searchBtn.addEventListener("click", ()=>{ 11 | sidebar.classList.toggle("open"); 12 | menuBtnChange(); 13 | }); 14 | 15 | function menuBtnChange() { 16 | if(sidebar.classList.contains("open")){ 17 | closeBtn.classList.replace("bx-menu", "bx-menu-alt-right"); 18 | }else { 19 | closeBtn.classList.replace("bx-menu-alt-right","bx-menu"); 20 | } 21 | } -------------------------------------------------------------------------------- /clientes/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Cliente(models.Model): 4 | nome = models.CharField(max_length=50) 5 | sobrenome = models.CharField(max_length=50) 6 | email = models.EmailField(max_length=50) 7 | cpf = models.CharField(max_length=12) 8 | 9 | def __str__(self) -> str: 10 | return self.nome 11 | 12 | class Carro(models.Model): 13 | carro = models.CharField(max_length=50) 14 | placa = models.CharField(max_length=8) 15 | ano = models.IntegerField() 16 | cliente = models.ForeignKey(Cliente, on_delete=models.CASCADE) 17 | lavagens = models.IntegerField(default=0) 18 | consertos = models.IntegerField(default=0) 19 | 20 | def __str__(self) -> str: 21 | return self.carro 22 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mecajato.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /templates/static/clientes/css/clientes.css: -------------------------------------------------------------------------------- 1 | .card-dashboard{ 2 | 3 | background-color: var(--main-color); 4 | width: 50%; 5 | margin-left: 10px; 6 | margin-top: 40px; 7 | text-align: center; 8 | padding: 20px; 9 | cursor: pointer; 10 | 11 | } 12 | 13 | .text-card{ 14 | 15 | font-size: 25px; 16 | 17 | } 18 | 19 | .btn-add-carros{ 20 | 21 | background-color: var(--contrast-color); 22 | padding: 5px; 23 | font-weight: bold; 24 | cursor: pointer; 25 | 26 | } 27 | 28 | .btn-principal{ 29 | color:white; 30 | border: none; 31 | background-color: var(--dark-color); 32 | padding: 5px; 33 | font-size: 30px; 34 | } 35 | 36 | #att_cliente{ 37 | 38 | display: none; 39 | } 40 | 41 | #form-att-cliente{ 42 | display: none; 43 | 44 | } -------------------------------------------------------------------------------- /servicos/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | from .models import Servico, CategoriaManutencao 3 | 4 | class FormServico(ModelForm): 5 | class Meta: 6 | model = Servico 7 | exclude = ['finalizado', 'protocole'] 8 | 9 | def __init__(self, *args, **kwargs): 10 | super().__init__(*args, **kwargs) 11 | for field in self.fields: 12 | self.fields[field].widget.attrs.update({'class': 'form-control'}) 13 | self.fields[field].widget.attrs.update({'placeholder': field}) 14 | 15 | 16 | choices = list() 17 | for i, j in self.fields['categoria_manutencao'].choices: 18 | categoria = CategoriaManutencao.objects.get(titulo=j) 19 | choices.append((i.value, categoria.get_titulo_display())) 20 | 21 | self.fields['categoria_manutencao'].choices = choices -------------------------------------------------------------------------------- /servicos/templates/novo_servico.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block 'head' %} 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block 'dashboard' %} 10 |
11 |
12 |
13 |
{% csrf_token %} 14 | 15 | {{form.non_field_errors}} 16 | 17 | {% for field in form %} 18 |
19 | {{field.errors}} 20 |
21 |
22 | {{field.label_tag}} 23 | 24 | {{field}} 25 |
26 | {% endfor %} 27 | 28 | 29 |
30 |
31 | 32 | {% endblock %} -------------------------------------------------------------------------------- /servicos/migrations/0004_servicoadicional_servico_servicos_adicionais.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.1 on 2023-08-21 19:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('servicos', '0003_servico_identificador'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='ServicoAdicional', 15 | fields=[ 16 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('titulo', models.CharField(max_length=50)), 18 | ('descricao', models.TextField()), 19 | ('preco', models.FloatField()), 20 | ], 21 | ), 22 | migrations.AddField( 23 | model_name='servico', 24 | name='servicos_adicionais', 25 | field=models.ManyToManyField(to='servicos.servicoadicional'), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /clientes/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.1 on 2022-09-09 00:11 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Cliente', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('nome', models.CharField(max_length=50)), 20 | ('sobrenome', models.CharField(max_length=50)), 21 | ('email', models.EmailField(max_length=50)), 22 | ('cpf', models.CharField(max_length=12)), 23 | ], 24 | ), 25 | migrations.CreateModel( 26 | name='Carro', 27 | fields=[ 28 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 29 | ('carro', models.CharField(max_length=50)), 30 | ('placa', models.CharField(max_length=8)), 31 | ('ano', models.IntegerField()), 32 | ('lavagens', models.IntegerField(default=0)), 33 | ('consertos', models.IntegerField(default=0)), 34 | ('cliente', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='clientes.cliente')), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /servicos/templates/listar_servico.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block 'head' %} 5 | 6 | 7 | {% endblock %} 8 | 9 | 10 | {% block 'dashboard' %} 11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for servico in servicos %} 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | {% endfor %} 41 | 42 |
IdentificadorTítuloClienteStatusPreço total
{{servico.identificador}}{{servico.titulo}}{{servico.cliente}} 31 | {% if servico.finalizado %} 32 | Finalizado 33 | {% else %} 34 | Em manutenção 35 | {% endif %} 36 | 37 | R$ {{servico.preco_total}}
43 |
44 | {% endblock %} -------------------------------------------------------------------------------- /servicos/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.1 on 2022-10-06 21:31 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('clientes', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='CategoriaManutencao', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('titulo', models.CharField(choices=[('TVM', 'Trocar válcula do motor'), ('TO', 'Troca de óleo'), ('B', 'Balanceamento')], max_length=3)), 21 | ('preco', models.DecimalField(decimal_places=2, max_digits=8)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Servico', 26 | fields=[ 27 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('titulo', models.CharField(max_length=30)), 29 | ('data_inicio', models.DateField(null=True)), 30 | ('data_entrega', models.DateField(null=True)), 31 | ('finalizado', models.BooleanField(default=False)), 32 | ('protocole', models.CharField(blank=True, max_length=32, null=True)), 33 | ('categoria_manutencao', models.ManyToManyField(to='servicos.categoriamanutencao')), 34 | ('cliente', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='clientes.cliente')), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /servicos/models.py: -------------------------------------------------------------------------------- 1 | from email.policy import default 2 | from secrets import token_hex, token_urlsafe 3 | from django.db import models 4 | from clientes.models import Cliente 5 | from .choices import ChoicesCategoriaManutencao 6 | from datetime import datetime 7 | 8 | class CategoriaManutencao(models.Model): 9 | titulo = models.CharField(max_length=3, choices=ChoicesCategoriaManutencao.choices) 10 | preco = models.DecimalField(max_digits=8, decimal_places=2) 11 | 12 | def __str__(self) -> str: 13 | return self.titulo 14 | 15 | class ServicoAdicional(models.Model): 16 | titulo = models.CharField(max_length=50) 17 | descricao = models.TextField() 18 | preco = models.FloatField() 19 | 20 | def __str__(self) -> str: 21 | return self.titulo 22 | 23 | class Servico(models.Model): 24 | titulo = models.CharField(max_length=30) 25 | cliente = models.ForeignKey(Cliente, on_delete=models.SET_NULL, null=True) 26 | categoria_manutencao = models.ManyToManyField(CategoriaManutencao) 27 | data_inicio = models.DateField(null=True) 28 | data_entrega = models.DateField(null=True) 29 | finalizado = models.BooleanField(default=False) 30 | protocole = models.CharField(max_length=52, null=True, blank=True) 31 | identificador = models.CharField(max_length=24, null=True, blank=True) 32 | servicos_adicionais = models.ManyToManyField(ServicoAdicional) 33 | 34 | def __str__(self) -> str: 35 | return self.titulo 36 | 37 | def save(self, *args, **kwargs): 38 | if not self.protocole: 39 | self.protocole = datetime.now().strftime("%d/%m/%Y-%H:%M:%S-") + token_hex(16) 40 | 41 | if not self.identificador: 42 | self.identificador = token_urlsafe(16) 43 | 44 | super(Servico, self).save(*args, **kwargs) 45 | 46 | def preco_total(self): 47 | preco_total = float(0) 48 | for categoria in self.categoria_manutencao.all(): 49 | preco_total += float(categoria.preco) 50 | 51 | return preco_total 52 | -------------------------------------------------------------------------------- /servicos/templates/servico.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block 'head' %} 5 | 6 | 7 | {% endblock %} 8 | 9 | 10 | {% block 'dashboard' %} 11 |
12 |
13 |
14 | GERAR OS 15 | 18 |
19 |
20 |

{{servico.titulo}}

21 | 22 |

23 | 24 | 25 | 26 | {% endblock %} 27 | 28 | 29 | {% block 'body' %} 30 | 59 | 60 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% block 'head' %}{% endblock %} 14 | 15 | {% block 'title' %}{% endblock %} 16 | 17 | 18 | 19 | 62 | 63 |
64 | {% block 'dashboard'%} 65 | {% endblock %} 66 | 67 |
68 | 69 | {% block 'body' %} 70 | {% endblock %} 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /servicos/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, get_object_or_404 2 | from .forms import FormServico 3 | from django.http import HttpResponse, FileResponse 4 | from .models import Servico, ServicoAdicional 5 | from fpdf import FPDF 6 | from io import BytesIO 7 | 8 | def novo_servico(request): 9 | if request.method == "GET": 10 | form = FormServico() 11 | return render(request, 'novo_servico.html', {'form': form}) 12 | elif request.method == "POST": 13 | form = FormServico(request.POST) 14 | if form.is_valid(): 15 | form.save() 16 | return HttpResponse('Salvo com sucesso') 17 | else: 18 | return render(request, 'novo_servico.html', {'form': form}) 19 | 20 | def listar_servico(request): 21 | if request.method == "GET": 22 | servicos = Servico.objects.all() 23 | return render(request, 'listar_servico.html', {'servicos': servicos}) 24 | 25 | def servico(request, identificador): 26 | servico = get_object_or_404(Servico, identificador=identificador) 27 | return render(request, 'servico.html', {'servico': servico}) 28 | 29 | def gerar_os(request, identificador): 30 | servico = get_object_or_404(Servico, identificador=identificador) 31 | 32 | pdf = FPDF() 33 | pdf.add_page() 34 | 35 | pdf.set_font('Arial', 'B', 12) 36 | 37 | pdf.set_fill_color(240,240,240) 38 | pdf.cell(35, 10, 'Cliente:', 1, 0, 'L', 1) 39 | pdf.cell(0, 10, f'{servico.cliente.nome}', 1, 1, 'L', 1) 40 | 41 | pdf.cell(35, 10, 'Manutenções:', 1, 0, 'L', 1) 42 | 43 | categorias_manutencao = servico.categoria_manutencao.all() 44 | for i, manutencao in enumerate(categorias_manutencao): 45 | pdf.cell(0, 10, f'- {manutencao.get_titulo_display()}', 1, 1, 'L', 1) 46 | if not i == len(categorias_manutencao) -1: 47 | pdf.cell(35, 10, '', 0, 0) 48 | 49 | pdf.cell(35, 10, 'Data de início:', 1, 0, 'L', 1) 50 | pdf.cell(0, 10, f'{servico.data_inicio}', 1, 1, 'L', 1) 51 | pdf.cell(35, 10, 'Data de entrega:', 1, 0, 'L', 1) 52 | pdf.cell(0, 10, f'{servico.data_entrega}', 1, 1, 'L', 1) 53 | pdf.cell(35, 10, 'Protocolo:', 1, 0, 'L', 1) 54 | pdf.cell(0, 10, f'{servico.protocole}', 1, 1, 'L', 1) 55 | 56 | pdf_content = pdf.output(dest='S').encode('latin1') 57 | pdf_bytes = BytesIO(pdf_content) 58 | 59 | return FileResponse(pdf_bytes, as_attachment=True, filename=f"os-{servico.protocole}.pdf") 60 | 61 | 62 | def servico_adicional(request): 63 | identificador_servico = request.POST.get('identificador_servico') 64 | titulo = request.POST.get('titulo') 65 | descricao = request.POST.get('descricao') 66 | preco = request.POST.get('preco') 67 | 68 | servico_adicional = ServicoAdicional(titulo=titulo, 69 | descricao=descricao, 70 | preco=preco) 71 | 72 | servico_adicional.save() 73 | 74 | servico = Servico.objects.get(identificador=identificador_servico) 75 | servico.servicos_adicionais.add(servico_adicional) 76 | servico.save() 77 | 78 | return HttpResponse("Salvo") -------------------------------------------------------------------------------- /mecajato/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os 3 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 4 | BASE_DIR = Path(__file__).resolve().parent.parent 5 | 6 | 7 | # Quick-start development settings - unsuitable for production 8 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 9 | 10 | # SECURITY WARNING: keep the secret key used in production secret! 11 | SECRET_KEY = 'django-insecure-jneo8fq9-_#3kddovv2^hs!%s5u_fi79%m+&5-%rujrh78up(o' 12 | 13 | # SECURITY WARNING: don't run with debug turned on in production! 14 | DEBUG = True 15 | 16 | ALLOWED_HOSTS = [] 17 | 18 | 19 | # Application definition 20 | 21 | INSTALLED_APPS = [ 22 | 'django.contrib.admin', 23 | 'django.contrib.auth', 24 | 'django.contrib.contenttypes', 25 | 'django.contrib.sessions', 26 | 'django.contrib.messages', 27 | 'django.contrib.staticfiles', 28 | 29 | # MyApps 30 | 'clientes', 31 | 'servicos', 32 | ] 33 | 34 | MIDDLEWARE = [ 35 | 'django.middleware.security.SecurityMiddleware', 36 | 'django.contrib.sessions.middleware.SessionMiddleware', 37 | 'django.middleware.common.CommonMiddleware', 38 | 'django.middleware.csrf.CsrfViewMiddleware', 39 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 40 | 'django.contrib.messages.middleware.MessageMiddleware', 41 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 42 | ] 43 | 44 | ROOT_URLCONF = 'mecajato.urls' 45 | 46 | TEMPLATES = [ 47 | { 48 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 49 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 50 | 'APP_DIRS': True, 51 | 'OPTIONS': { 52 | 'context_processors': [ 53 | 'django.template.context_processors.debug', 54 | 'django.template.context_processors.request', 55 | 'django.contrib.auth.context_processors.auth', 56 | 'django.contrib.messages.context_processors.messages', 57 | ], 58 | }, 59 | }, 60 | ] 61 | 62 | WSGI_APPLICATION = 'mecajato.wsgi.application' 63 | 64 | 65 | # Database 66 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 67 | 68 | DATABASES = { 69 | 'default': { 70 | 'ENGINE': 'django.db.backends.sqlite3', 71 | 'NAME': BASE_DIR / 'db.sqlite3', 72 | } 73 | } 74 | 75 | 76 | # Password validation 77 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 78 | 79 | AUTH_PASSWORD_VALIDATORS = [ 80 | { 81 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 82 | }, 83 | { 84 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 85 | }, 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 91 | }, 92 | ] 93 | 94 | 95 | # Internationalization 96 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 97 | 98 | LANGUAGE_CODE = 'pt-BR' 99 | 100 | TIME_ZONE = 'America/Sao_Paulo' 101 | 102 | USE_I18N = True 103 | 104 | USE_TZ = True 105 | 106 | 107 | # Static files (CSS, JavaScript, Images) 108 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 109 | 110 | STATIC_URL = 'static/' 111 | STATICFILES_DIRS = (os.path.join(BASE_DIR, 'templates/static'),) 112 | STATIC_ROOT = os.path.join('static') 113 | 114 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 115 | MEDIA_URL = "/media/" 116 | 117 | 118 | 119 | # Default primary key field type 120 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 121 | 122 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /clientes/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse, JsonResponse 3 | from .models import Cliente, Carro 4 | import re 5 | from django.core import serializers 6 | import json 7 | from django.shortcuts import redirect, get_object_or_404 8 | from django.urls import reverse 9 | from django.views.decorators.csrf import csrf_exempt 10 | 11 | def clientes(request): 12 | if request.method == "GET": 13 | clientes_list = Cliente.objects.all() 14 | return render(request, 'clientes.html', {'clientes': clientes_list}) 15 | elif request.method == "POST": 16 | nome = request.POST.get('nome') 17 | sobrenome = request.POST.get('sobrenome') 18 | email = request.POST.get('email') 19 | cpf = request.POST.get('cpf') 20 | carros = request.POST.getlist('carro') 21 | placas = request.POST.getlist('placa') 22 | anos = request.POST.getlist('ano') 23 | 24 | cliente = Cliente.objects.filter(cpf=cpf) 25 | 26 | if cliente.exists(): 27 | return render(request, 'clientes.html', {'nome': nome, 'sobrenome': sobrenome, 'email': email, 'carros': zip(carros, placas, anos) }) 28 | 29 | if not re.fullmatch(re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+'), email): 30 | return render(request, 'clientes.html', {'nome': nome, 'sobrenome': sobrenome, 'cpf': cpf, 'carros': zip(carros, placas, anos)}) 31 | 32 | cliente = Cliente( 33 | nome = nome, 34 | sobrenome = sobrenome, 35 | email = email, 36 | cpf = cpf 37 | ) 38 | 39 | cliente.save() 40 | 41 | for carro, placa, ano in zip(carros, placas, anos): 42 | car = Carro(carro=carro, placa=placa, ano=ano, cliente=cliente) 43 | car.save() 44 | 45 | return HttpResponse('Teste') 46 | 47 | 48 | def att_cliente(request): 49 | id_cliente = request.POST.get('id_cliente') 50 | cliente = Cliente.objects.filter(id=id_cliente) 51 | carros = Carro.objects.filter(cliente=cliente[0]) 52 | cliente_json = json.loads(serializers.serialize('json', cliente))[0]['fields'] 53 | cliente_id = json.loads(serializers.serialize('json', cliente))[0]['pk'] 54 | carros_json = json.loads(serializers.serialize('json', carros)) 55 | carros_json = [{'fields': i['fields'], 'id': i['pk']} for i in carros_json] 56 | data = {'cliente': cliente_json, 'carros': carros_json, 'cliente_id': cliente_id} 57 | return JsonResponse(data) 58 | 59 | def excluir_carro(request, id): 60 | try: 61 | carro = Carro.objects.get(id=id) 62 | carro.delete() 63 | return redirect(reverse('clientes')+f'?aba=att_cliente&id_cliente={id}') 64 | except: 65 | return redirect(reverse('clientes')+f'?aba=att_cliente&id_cliente={id}') 66 | 67 | @csrf_exempt 68 | def update_carro(request, id): 69 | nome_carro = request.POST.get('carro') 70 | placa = request.POST.get('placa') 71 | ano = request.POST.get('ano') 72 | 73 | carro = Carro.objects.get(id=id) 74 | list_carros = Carro.objects.exclude(id=id).filter(placa=placa) 75 | 76 | if list_carros.exists(): 77 | return HttpResponse('Placa já existente') 78 | 79 | carro.carro = nome_carro 80 | carro.placa = placa 81 | carro.ano = ano 82 | carro.save() 83 | 84 | return HttpResponse(id) 85 | 86 | def update_cliente(request, id): 87 | body = json.loads(request.body) 88 | 89 | nome = body['nome'] 90 | sobrenome = body['sobrenome'] 91 | email = body['email'] 92 | cpf = body['cpf'] 93 | 94 | cliente = get_object_or_404(Cliente, id=id) 95 | try: 96 | cliente.nome = nome 97 | cliente.sobrenome = sobrenome 98 | cliente.email = email 99 | cliente.cpf = cpf 100 | cliente.save() 101 | return JsonResponse({'status': '200', 'nome': nome, 'sobrenome': sobrenome, 'email': email, 'cpf': cpf}) 102 | except: 103 | return JsonResponse({'status': '500'}) 104 | 105 | -------------------------------------------------------------------------------- /templates/static/clientes/js/clientes.js: -------------------------------------------------------------------------------- 1 | function add_carro(){ 2 | container = document.getElementById('form-carro') 3 | 4 | html = "
" 5 | 6 | container.innerHTML += html 7 | } 8 | 9 | function exibir_form(tipo){ 10 | 11 | add_cliente = document.getElementById('adicionar-cliente') 12 | att_cliente = document.getElementById('att_cliente') 13 | 14 | if(tipo == "1"){ 15 | att_cliente.style.display = "none" 16 | add_cliente.style.display = "block" 17 | 18 | }else if(tipo == "2"){ 19 | add_cliente.style.display = "none"; 20 | att_cliente.style.display = "block" 21 | } 22 | 23 | } 24 | 25 | 26 | function dados_cliente(){ 27 | cliente = document.getElementById('cliente-select') 28 | csrf_token = document.querySelector('[name=csrfmiddlewaretoken]').value 29 | id_cliente = cliente.value 30 | 31 | data = new FormData() 32 | data.append('id_cliente', id_cliente) 33 | 34 | fetch("/clientes/atualiza_cliente/",{ 35 | method: "POST", 36 | headers: { 37 | 'X-CSRFToken': csrf_token, 38 | }, 39 | body: data 40 | 41 | }).then(function(result){ 42 | return result.json() 43 | }).then(function(data){ 44 | document.getElementById('form-att-cliente').style.display = 'block' 45 | 46 | id = document.getElementById('id') 47 | id.value = data['cliente_id'] 48 | 49 | nome = document.getElementById('nome') 50 | nome.value = data['cliente']['nome'] 51 | 52 | sobrenome = document.getElementById('sobrenome') 53 | sobrenome.value = data['cliente']['sobrenome'] 54 | 55 | cpf = document.getElementById('cpf') 56 | cpf.value = data['cliente']['cpf'] 57 | 58 | email = document.getElementById('email') 59 | email.value = data['cliente']['email'] 60 | 61 | div_carros = document.getElementById('carros') 62 | 63 | for(i=0; i\ 65 |
\ 66 |
\ 67 | \ 68 |
\ 69 |
\ 70 | \ 71 |
\ 72 |
\ 73 | \ 74 |
\ 75 |
\ 76 | \ 77 |
\ 78 | \ 79 |
\ 80 | EXCLUIR\ 81 |
\ 82 |

" 83 | } 84 | 85 | }) 86 | 87 | 88 | } 89 | 90 | 91 | function update_cliente(){ 92 | nome = document.getElementById('nome').value 93 | sobrenome = document.getElementById('sobrenome').value 94 | email = document.getElementById('email').value 95 | cpf = document.getElementById('cpf').value 96 | id = document.getElementById('id').value 97 | 98 | fetch('/clientes/update_cliente/' + id, { 99 | method: 'POST', 100 | headers: { 101 | 'X-CSRFToken': csrf_token, 102 | }, 103 | body: JSON.stringify({ 104 | nome: nome, 105 | sobrenome: sobrenome, 106 | email: email, 107 | cpf: cpf, 108 | }) 109 | 110 | }).then(function(result){ 111 | return result.json() 112 | }).then(function(data){ 113 | 114 | if(data['status'] == '200'){ 115 | nome = data['nome'] 116 | sobrenome = data['sobrenome'] 117 | email = data['email'] 118 | cpf = data['cpf'] 119 | console.log('Dados alterado com sucesso') 120 | }else{ 121 | console.log('Ocorreu algum erro') 122 | } 123 | 124 | }) 125 | 126 | } 127 | -------------------------------------------------------------------------------- /clientes/templates/clientes.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block 'head' %} 5 | 6 | {% endblock %} 7 | 8 | {% block 'dashboard' %} 9 |
10 |
11 |
12 |

Adicionar clientes

13 |
14 | 15 |
16 |

Atualizar clientes

17 |
18 | 19 |
20 | 21 |
22 |
{% csrf_token %} 23 |
24 |
25 |

Nome:

26 | 27 |
28 |
29 |

Sobrenome:

30 | 31 |
32 |
33 |
34 |

E-mail:

35 | 36 |
37 |

CPF:

38 | 39 |
40 |
41 |

Carros

42 | + Adicionar um carro 43 | #TODO: remover carros 44 |
45 | {% for carro, placa, ano in carros%} 46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | {% endfor%} 62 |
63 | 64 |
65 | 66 |
67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 |
75 |

76 | 81 |
82 |
83 | 84 |
85 | 86 |

Nome:

87 | 88 |

Sobrenome:

89 | 90 |

E-mail:

91 | 92 |

CPF:

93 | 94 |
95 | 96 |
97 |

Carros

98 |
99 | 100 |
101 |
102 | 103 |
104 | 105 |
106 | 107 | {% endblock%} -------------------------------------------------------------------------------- /templates/static/general/css/base.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap'); 2 | *{ 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: "Poppins" , sans-serif; 7 | } 8 | .sidebar{ 9 | position: fixed; 10 | left: 0; 11 | top: 0; 12 | height: 100%; 13 | width: 78px; 14 | background: var(--dark-color); 15 | padding: 6px 14px; 16 | z-index: 99; 17 | transition: all 0.5s ease; 18 | } 19 | .sidebar.open{ 20 | width: 250px; 21 | } 22 | .sidebar .logo-details{ 23 | height: 60px; 24 | display: flex; 25 | align-items: center; 26 | position: relative; 27 | } 28 | .sidebar .logo-details .icon{ 29 | opacity: 0; 30 | transition: all 0.5s ease; 31 | } 32 | .sidebar .logo-details .logo_name{ 33 | color: #fff; 34 | font-size: 20px; 35 | font-weight: 600; 36 | opacity: 0; 37 | transition: all 0.5s ease; 38 | } 39 | .sidebar.open .logo-details .icon, 40 | .sidebar.open .logo-details .logo_name{ 41 | opacity: 1; 42 | } 43 | .sidebar .logo-details #btn{ 44 | position: absolute; 45 | top: 50%; 46 | right: 0; 47 | transform: translateY(-50%); 48 | font-size: 22px; 49 | transition: all 0.4s ease; 50 | font-size: 23px; 51 | text-align: center; 52 | cursor: pointer; 53 | transition: all 0.5s ease; 54 | } 55 | .sidebar.open .logo-details #btn{ 56 | text-align: right; 57 | } 58 | .sidebar i{ 59 | color: #fff; 60 | height: 60px; 61 | min-width: 50px; 62 | font-size: 28px; 63 | text-align: center; 64 | line-height: 60px; 65 | } 66 | .sidebar .nav-list{ 67 | margin-top: 20px; 68 | height: 100%; 69 | } 70 | .sidebar li{ 71 | position: relative; 72 | margin: 8px 0; 73 | list-style: none; 74 | } 75 | .sidebar li .tooltip{ 76 | position: absolute; 77 | top: -20px; 78 | left: calc(100% + 15px); 79 | z-index: 3; 80 | background: #fff; 81 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); 82 | padding: 6px 12px; 83 | border-radius: 4px; 84 | font-size: 15px; 85 | font-weight: 400; 86 | opacity: 0; 87 | white-space: nowrap; 88 | pointer-events: none; 89 | transition: 0s; 90 | } 91 | .sidebar li:hover .tooltip{ 92 | opacity: 1; 93 | pointer-events: auto; 94 | transition: all 0.4s ease; 95 | top: 50%; 96 | transform: translateY(-50%); 97 | } 98 | .sidebar.open li .tooltip{ 99 | display: none; 100 | } 101 | .sidebar input{ 102 | font-size: 15px; 103 | color: #FFF; 104 | font-weight: 400; 105 | outline: none; 106 | height: 50px; 107 | width: 100%; 108 | width: 50px; 109 | border: none; 110 | border-radius: 12px; 111 | transition: all 0.5s ease; 112 | background: var(--main-color); 113 | } 114 | .sidebar.open input{ 115 | padding: 0 20px 0 50px; 116 | width: 100%; 117 | } 118 | .sidebar .bx-search{ 119 | position: absolute; 120 | top: 50%; 121 | left: 0; 122 | transform: translateY(-50%); 123 | font-size: 22px; 124 | background: var(--main-color); 125 | color: #FFF; 126 | } 127 | .sidebar.open .bx-search:hover{ 128 | background: var(--main-color); 129 | color: #FFF; 130 | } 131 | .sidebar .bx-search:hover{ 132 | background: #FFF; 133 | color: var(--main-color); 134 | } 135 | .sidebar li a{ 136 | display: flex; 137 | height: 100%; 138 | width: 100%; 139 | border-radius: 12px; 140 | align-items: center; 141 | text-decoration: none; 142 | transition: all 0.4s ease; 143 | background: var(--main-color); 144 | } 145 | .sidebar li a:hover{ 146 | background: #FFF; 147 | } 148 | .sidebar li a .links_name{ 149 | color: #fff; 150 | font-size: 15px; 151 | font-weight: 400; 152 | white-space: nowrap; 153 | opacity: 0; 154 | pointer-events: none; 155 | transition: 0.4s; 156 | } 157 | .sidebar.open li a .links_name{ 158 | opacity: 1; 159 | pointer-events: auto; 160 | } 161 | .sidebar li a:hover .links_name, 162 | .sidebar li a:hover i{ 163 | transition: all 0.5s ease; 164 | color: var(--main-color); 165 | } 166 | .sidebar li i{ 167 | height: 50px; 168 | line-height: 50px; 169 | font-size: 18px; 170 | border-radius: 12px; 171 | } 172 | .sidebar li.profile{ 173 | position: fixed; 174 | height: 60px; 175 | width: 78px; 176 | left: 0; 177 | bottom: -8px; 178 | padding: 10px 14px; 179 | background: var(--main-color); 180 | transition: all 0.5s ease; 181 | overflow: hidden; 182 | } 183 | .sidebar.open li.profile{ 184 | width: 250px; 185 | } 186 | .sidebar li .profile-details{ 187 | display: flex; 188 | align-items: center; 189 | flex-wrap: nowrap; 190 | } 191 | .sidebar li img{ 192 | height: 45px; 193 | width: 45px; 194 | object-fit: cover; 195 | border-radius: 6px; 196 | margin-right: 10px; 197 | } 198 | .sidebar li.profile .name, 199 | .sidebar li.profile .job{ 200 | font-size: 15px; 201 | font-weight: 400; 202 | color: #fff; 203 | white-space: nowrap; 204 | } 205 | .sidebar li.profile .job{ 206 | font-size: 12px; 207 | } 208 | .sidebar .profile #log_out{ 209 | position: absolute; 210 | top: 50%; 211 | right: 0; 212 | transform: translateY(-50%); 213 | background: var(--main-color); 214 | width: 100%; 215 | height: 60px; 216 | line-height: 60px; 217 | border-radius: 0px; 218 | transition: all 0.5s ease; 219 | } 220 | .sidebar.open .profile #log_out{ 221 | width: 50px; 222 | background: none; 223 | } 224 | .home-section{ 225 | position: relative; 226 | min-height: 100vh; 227 | top: 0; 228 | background: #2A2A2A; 229 | left: 78px; 230 | color: white; 231 | width: calc(100% - 78px); 232 | transition: all 0.5s ease; 233 | z-index: 2; 234 | } 235 | .sidebar.open ~ .home-section{ 236 | left: 250px; 237 | width: calc(100% - 250px); 238 | } 239 | .home-section .text{ 240 | display: inline-block; 241 | color: var(--main-color); 242 | font-size: 25px; 243 | font-weight: 500; 244 | margin: 18px 245 | } 246 | @media (max-width: 420px) { 247 | .sidebar li .tooltip{ 248 | display: none; 249 | } 250 | } --------------------------------------------------------------------------------