diff --git a/sigi/apps/home/admin.py b/sigi/apps/home/admin.py new file mode 100644 index 0000000..772c4fd --- /dev/null +++ b/sigi/apps/home/admin.py @@ -0,0 +1,29 @@ +from django.contrib import admin +from sigi.apps.home.models import Cards, Dashboard + + +@admin.register(Cards) +class CardAdmin(admin.ModelAdmin): + list_display = ("codigo", "titulo", "categoria", "ordem", "default") + list_editable = ("titulo", "categoria", "ordem", "default") + search_fields = ("titulo", "codigo") + + +@admin.register(Dashboard) +class DashboardAdmin(admin.ModelAdmin): + list_display = ("card", "categoria", "ordem") + exclude = ("usuario",) + list_editable = ("categoria", "ordem") + autocomplete_fields = ("card",) + + def save_model(self, request, obj, form, change): + if not change: + obj.usuario = request.user + if obj.categoria == "": + obj.categoria = obj.card.categoria + if obj.ordem == 0: + obj.ordem = obj.card.ordem + return super().save_model(request, obj, form, change) + + def get_queryset(self, request): + return super().get_queryset(request).filter(usuario=request.user) diff --git a/sigi/apps/home/apps.py b/sigi/apps/home/apps.py new file mode 100644 index 0000000..a968999 --- /dev/null +++ b/sigi/apps/home/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class HomeConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "sigi.apps.home" + verbose_name = _("Home page do SIGI") diff --git a/sigi/apps/home/context_processors.py b/sigi/apps/home/context_processors.py new file mode 100644 index 0000000..6129188 --- /dev/null +++ b/sigi/apps/home/context_processors.py @@ -0,0 +1,57 @@ +from django.urls import reverse +from django.utils.text import slugify +from sigi.apps.home.models import Cards, Dashboard + + +def get_or_create_dash(usuario): + my_dash = Dashboard.objects.filter(usuario=usuario) + if my_dash.exists(): + return [ + { + "slug": slugify(categoria), + "label": categoria, + "cards": [d.card for d in my_dash.filter(categoria=categoria)], + } + for categoria in sorted( + set(my_dash.values_list("categoria", flat=True)) + ) + ] + else: + cards = Cards.objects.filter(default=True) + my_dash = [ + { + "slug": slugify(categoria), + "label": categoria, + "cards": [card for card in cards.filter(categoria=categoria)], + } + for categoria in sorted( + set(cards.values_list("categoria", flat=True)) + ) + ] + if not usuario.is_anonymous: + for card in cards: + Dashboard( + usuario=usuario, + card=card, + categoria=card.categoria, + ordem=card.ordem, + ).save() + return my_dash + + +def dashboard(request): + if request.path != reverse("admin:index"): + return {} + my_dash = get_or_create_dash(request.user) + selected = request.GET.get("dash", my_dash[0]["slug"]) + return { + "sigi_dashes": my_dash, + "sigi_dash_selected": selected, + "sigi_dash_all_categories": [ + (slugify(c), c) + for c in sorted( + set(Cards.objects.all().values_list("categoria", flat=True)) + ) + ], + "sigi_dash_all_cards": Cards.objects.all(), + } diff --git a/sigi/apps/home/fixtures/cards.json b/sigi/apps/home/fixtures/cards.json new file mode 100644 index 0000000..e85dc54 --- /dev/null +++ b/sigi/apps/home/fixtures/cards.json @@ -0,0 +1 @@ +[{"model": "home.cards", "pk": 1, "fields": {"codigo": "resumoseit", "tipo": "T", "nome_url": "home_resumoseit", "query_string": "", "link_acao": false, "titulo": "Serviços hospedados no Interlegis (SEIT)", "descricao": "Tabela com os serviços criados no SEIT por mês", "categoria": "Serviços", "ordem": 1, "default": true}}, {"model": "home.cards", "pk": 2, "fields": {"codigo": "chartseit", "tipo": "C", "nome_url": "home_chartseit", "query_string": "", "link_acao": true, "titulo": "Sazonalidade da hospedagem de serviços", "descricao": "Gráfico com a sazonalidade das solicitações de serviços", "categoria": "Serviços", "ordem": 2, "default": true}}, {"model": "home.cards", "pk": 3, "fields": {"codigo": "carteira", "tipo": "T", "nome_url": "casas-carteira", "query_string": "snippet=resumo&s=sim", "link_acao": false, "titulo": "Resumo da carteira de relacionamentos", "descricao": "Tabela resumindo a carteira de gerência Interlegis", "categoria": "Gerente", "ordem": 1, "default": true}}, {"model": "home.cards", "pk": 4, "fields": {"codigo": "performance", "tipo": "C", "nome_url": "home_chartperformance", "query_string": "", "link_acao": true, "titulo": "Performance da gerência de carteiras", "descricao": "Performance da gerência de carteiras", "categoria": "Gerente", "ordem": 2, "default": true}}, {"model": "home.cards", "pk": 5, "fields": {"codigo": "chartcarteira", "tipo": "C", "nome_url": "home_chartcarteira", "query_string": "", "link_acao": false, "titulo": "Distribuição de Casas por Gerente", "descricao": "Distribuição de Casas por Gerente", "categoria": "Gerente", "ordem": 3, "default": true}}, {"model": "home.cards", "pk": 6, "fields": {"codigo": "resumoconvenios", "tipo": "T", "nome_url": "home_resumoconvenios", "query_string": "", "link_acao": false, "titulo": "Resumo de informações", "descricao": "Resumo de informações", "categoria": "Geral", "ordem": 1, "default": true}}] \ No newline at end of file diff --git a/sigi/apps/home/migrations/0001_initial.py b/sigi/apps/home/migrations/0001_initial.py new file mode 100644 index 0000000..2717e49 --- /dev/null +++ b/sigi/apps/home/migrations/0001_initial.py @@ -0,0 +1,53 @@ +# Generated by Django 4.0.4 on 2022-05-11 14:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Cards', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('codigo', models.CharField(max_length=20, verbose_name='código')), + ('tipo', models.CharField(choices=[('T', 'Tabela de dados'), ('C', 'Gráfico')], max_length=1, verbose_name='tipo')), + ('nome_url', models.CharField(max_length=30, verbose_name='nome da URL de dados')), + ('query_string', models.CharField(blank=True, max_length=100, verbose_name='query string')), + ('link_acao', models.BooleanField(default=False, verbose_name='possui link de ação')), + ('titulo', models.CharField(max_length=100, verbose_name='título')), + ('descricao', models.TextField(verbose_name='descrição')), + ('categoria', models.CharField(default='Geral', max_length=40, verbose_name='categoria')), + ('ordem', models.PositiveSmallIntegerField(default=0, verbose_name='posição na categoria')), + ('default', models.BooleanField(default=False, help_text='Indica se este card deve ser mostrado para usuários anônimos ou que não personalizaram seu dashboard', verbose_name='card padrão')), + ], + options={ + 'verbose_name': 'card', + 'verbose_name_plural': 'cards', + 'ordering': ('categoria', 'ordem', 'titulo'), + }, + ), + migrations.CreateModel( + name='Dashboard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('categoria', models.CharField(blank=True, help_text='Deixando em branco será utilizada a categoria padrão do card', max_length=40, verbose_name='categoria personalizada')), + ('ordem', models.PositiveSmallIntegerField(default=0, verbose_name='posição na categoria')), + ('card', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.cards')), + ('usuario', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'dashboard', + 'verbose_name_plural': 'dashboards', + 'ordering': ('usuario', 'categoria', 'ordem'), + }, + ), + ] diff --git a/sigi/apps/home/migrations/__init__.py b/sigi/apps/home/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/home/models.py b/sigi/apps/home/models.py new file mode 100644 index 0000000..19fc30a --- /dev/null +++ b/sigi/apps/home/models.py @@ -0,0 +1,69 @@ +from django.db import models +from django.conf import settings +from django.utils.translation import gettext as _ + + +class Cards(models.Model): + TIPO_CHOICES = ( + ("T", _("Tabela de dados")), + ("C", _("Gráfico")), + ) + codigo = models.CharField(_("código"), max_length=20) + tipo = models.CharField(_("tipo"), max_length=1, choices=TIPO_CHOICES) + nome_url = models.CharField(_("nome da URL de dados"), max_length=30) + query_string = models.CharField( + _("query string"), + max_length=100, + blank=True, + ) + link_acao = models.BooleanField(_("possui link de ação"), default=False) + titulo = models.CharField(_("título"), max_length=100) + descricao = models.TextField(_("descrição")) + categoria = models.CharField( + _("categoria"), max_length=40, default=_("Geral") + ) + ordem = models.PositiveSmallIntegerField( + _("posição na categoria"), default=0 + ) + default = models.BooleanField( + _("card padrão"), + default=False, + help_text=_( + "Indica se este card deve ser mostrado para usuários anônimos ou " + "que não personalizaram seu dashboard" + ), + ) + + class Meta: + ordering = ("categoria", "ordem", "titulo") + verbose_name = _("card") + verbose_name_plural = _("cards") + + def __str__(self): + return _(f"{self.titulo} ({self.categoria})") + + +class Dashboard(models.Model): + usuario = models.ForeignKey( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE + ) + card = models.ForeignKey(Cards, on_delete=models.CASCADE) + categoria = models.CharField( + _("categoria personalizada"), + max_length=40, + blank=True, + help_text=_( + "Deixando em branco será utilizada a categoria padrão do card" + ), + ) + ordem = models.PositiveSmallIntegerField( + _("posição na categoria"), default=0 + ) + + class Meta: + ordering = ("usuario", "categoria", "ordem") + verbose_name = _("dashboard") + verbose_name_plural = _("dashboards") + + def __str__(self): + return _(f"{self.usuario.get_full_name()} - {self.card}") diff --git a/sigi/apps/home/templates/home/dashboard/card.html b/sigi/apps/home/templates/home/dashboard/card.html new file mode 100644 index 0000000..699554a --- /dev/null +++ b/sigi/apps/home/templates/home/dashboard/card.html @@ -0,0 +1,44 @@ +{% load i18n static material %} +{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} + + + {{ card.titulo }} + + + + + + + + + + + + + + + + + + + +
+
+ {% include 'sigi/snippets/dashboard.html' %} +
+
+ +
+ diff --git a/sigi/apps/home/tests.py b/sigi/apps/home/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/sigi/apps/home/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sigi/apps/home/urls.py b/sigi/apps/home/urls.py index c1fb0c3..ae624e3 100644 --- a/sigi/apps/home/urls.py +++ b/sigi/apps/home/urls.py @@ -28,6 +28,32 @@ urlpatterns = [ views.report_sem_convenio, name="home_reportsemconvenio", ), + path( + "home/dashboard/card//", + views.card_snippet, + name="home_cardsnippet", + ), + path( + "home/dashboard/addtab//", + views.card_add_tab, + name="home_card_add_tab", + ), + path( + "home/dashboard/changetab/", + views.card_rename_tab, + name="home_card_rename_tab", + ), + path( + "home/dashboard/reorder/", + views.card_reorder, + name="home_card_reorder", + ), + path( + "home/dashboard/remove///", + views.card_remove, + name="home_card_remove", + ), + path("home/dashboard/addcard/", views.card_add, name="home_add_card"), ] # from django.conf.urls import patterns, url diff --git a/sigi/apps/home/views.py b/sigi/apps/home/views.py index dad1c4e..c05f8d4 100644 --- a/sigi/apps/home/views.py +++ b/sigi/apps/home/views.py @@ -2,19 +2,28 @@ import calendar import csv import datetime from itertools import cycle +from django.contrib import messages from django.contrib.admin.sites import site from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied from django.db import models from django.db.models import Q, Count -from django.http import HttpResponse, HttpResponseForbidden, JsonResponse +from django.http import ( + HttpResponse, + HttpResponseForbidden, + HttpResponseRedirect, + JsonResponse, +) from django.shortcuts import render, get_object_or_404 from django.template.loader import render_to_string +from django.utils.text import slugify from django.utils.translation import gettext as _ from django.views.decorators.cache import never_cache from django_weasyprint.views import WeasyTemplateResponse from sigi.apps.casas.models import TipoOrgao, Orgao from sigi.apps.contatos.models import UnidadeFederativa from sigi.apps.convenios.models import Convenio, Projeto +from sigi.apps.home.models import Cards, Dashboard from sigi.apps.servicos.models import TipoServico from sigi.apps.servidores.models import Servidor from sigi.apps.utils import to_ascii @@ -227,6 +236,91 @@ def openmapsearch(request): return JsonResponse(list(dados), safe=False) +def card_snippet(request, card_code): + card = get_object_or_404(Cards, codigo=card_code) + if not card.default: + raise PermissionDenied() + return render(request, "home/dashboard/card.html", {"dash_cards": [card]}) + + +@login_required +def card_add_tab(request, tab_slug): + for card in Cards.objects.all(): + if slugify(card.categoria) == tab_slug: + Dashboard( + usuario=request.user, + card=card, + categoria=card.categoria, + ordem=card.ordem, + ).save() + return HttpResponseRedirect(reverse("admin:index")) + + +@login_required +def card_rename_tab(request): + dados = request.POST.copy() + dados.pop("csrfmiddlewaretoken") + categoria_atual = dados.pop("categoria_atual")[0] + categoria_nova = dados.pop("categoria_nova")[0] + if categoria_nova != "" and categoria_nova != categoria_atual: + Dashboard.objects.filter( + usuario=request.user, categoria=categoria_atual + ).update(categoria=categoria_nova) + messages.success(request, _("Tab renomeada com sucesso")) + else: + messages.warning(request, _("Não foi possível renomear a tab")) + return HttpResponseRedirect(reverse("admin:index")) + + +@login_required +def card_reorder(request): + dados = request.GET.copy() + categoria = dados.pop("categoria")[0] + for codigo, nova_ordem in dados.items(): + Dashboard.objects.filter( + usuario=request.user, card__codigo=codigo, categoria=categoria + ).update(ordem=nova_ordem) + return JsonResponse({"result": "Done"}) + + +@login_required +def card_remove(request, categoria, codigo): + dash = get_object_or_404( + Dashboard, categoria=categoria, card__codigo=codigo + ) + dash.delete() + messages.success(request, _("Card removido")) + return HttpResponseRedirect(reverse("admin:index")) + + +@login_required +def card_add(request): + categoria = request.POST.get("categoria", None) + codigos = request.POST.getlist("card_id", None) + + if categoria is None or codigos is None: + messages.error(request, _("Nenhum card adicionado!")) + else: + criados = 0 + for codigo in codigos: + card = get_object_or_404(Cards, codigo=codigo) + dash, created = Dashboard.objects.get_or_create( + {"ordem": card.ordem}, + usuario=request.user, + card=card, + categoria=categoria, + ) + if created: + criados += 1 + if criados > 0: + messages.success( + request, _(f"{criados} card(s) adicionado(s) na aba") + ) + else: + messages.info(request, _("Estes cards já estão na aba")) + return HttpResponseRedirect(reverse("admin:index")) + + # @never_cache # @login_required # def index(request): diff --git a/sigi/settings.py b/sigi/settings.py index ae9759f..8277aae 100644 --- a/sigi/settings.py +++ b/sigi/settings.py @@ -110,6 +110,7 @@ TEMPLATES = [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "django.template.context_processors.media", + "sigi.apps.home.context_processors.dashboard", ], }, }, diff --git a/sigi/static/css/dashboard.css b/sigi/static/css/dashboard.css index b418f52..b9dc7c7 100644 --- a/sigi/static/css/dashboard.css +++ b/sigi/static/css/dashboard.css @@ -5,6 +5,7 @@ table.servicos { table.numeros>tbody>tr>td { text-align: right; } + .app>.card>.card-content>.card-title { font-size: 16px; font-weight: bolder; @@ -13,7 +14,7 @@ table.numeros>tbody>tr>td { .full-preloader { width: 100%; height: 100%; - background-color: rgb(255,255,255,0.8); + background-color: rgb(255, 255, 255, 0.8); position: absolute; z-index: 2; display: flex; @@ -34,4 +35,91 @@ table.numeros>tbody>tr>td { .card-links { padding: 0 24px; +} + +.tab-edit, +.tab-control { + display: inline-block; + text-align: center; + line-height: 48px; + height: 48px; + padding: 0; + margin: 0; + text-transform: uppercase; +} + +.tab-edit a, +.tab-control a { + display: block; + width: 100%; + height: 100%; + padding: 0 24px; + font-size: 14px; + text-overflow: ellipsis; + overflow: hidden; + transition: color .28s ease, background-color .28s ease; +} + +.dashbar { + background: var(--main-hover-color); + opacity: .8; + color: #fff; + padding: 10px; + border: none; + text-align: left; +} + +.dashtabs { + position: relative; + overflow-x: auto; + overflow-y: hidden; + height: 48px; + width: 100%; + background-color: #fff; + margin: 0 auto; + white-space: nowrap; + list-style-type: none; + display: block; + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + padding-inline-start: 40px; +} + +.dashtabs .tabitem { + display: inline-block; + font-weight: normal; + height: 48px; + line-height: 48px; + margin: 0; + padding: 0; + text-transform: uppercase; + text-align: center; +} + +.dashtabs .tabitem .active { + font-weight: bolder; +} + +.dashtabs .tabitem a, +.dashtabs .tabitem p { + display: block; + width: 100%; + height: 100%; + padding: 0 24px; + font-size: 14px; + text-overflow: ellipsis; + overflow: hidden; +} + +.dashtabs .tabitem i { + line-height: unset; + color: var(--primary); +} + +.dash-modal-header { + font-size: 1.2em; + display: block; + text-transform: uppercase; } \ No newline at end of file diff --git a/sigi/static/js/dashboard.js b/sigi/static/js/dashboard.js index 793f664..9dc9d2f 100644 --- a/sigi/static/js/dashboard.js +++ b/sigi/static/js/dashboard.js @@ -1,12 +1,35 @@ $(document).ready(function () { + M.Tabs.init($('.tabs'), {}); + $(".dash-control").hide(); + $(".tab-edit a").off("click").on("click", function (e) { + e.preventDefault(); + $(".dash-control").toggle(); + if ($(".dash-control").is(':visible')) { + $(".sortable").sortable({ + update: function (e, ui) { + var parent = ui.item.parent(); + var url = parent.attr("data-target-url"); + var dados = { 'categoria': parent.attr("data-tab-name") }; + parent.children().each(function (pos) { + dados[$(this).attr("data-card-id")] = pos + 1; + }) + $.get(url, dados, function () { + M.toast({ html: 'Ordem alterada' }) + }); + } + }); + } else { + $(".sortable").sortable("disable"); + } + }) Chart.defaults.plugins.legend.labels.usePointStyle = true; setlinks(); - $("div[data-source]").each(function(index, container) { + $("div[data-source]").each(function (index, container) { var container = $(container); var url = container.attr('data-source'); get_content(container, url); }); - $("canvas[data-source]").each(function(index, canvas) { + $("canvas[data-source]").each(function (index, canvas) { var canvas = $(canvas) var url = canvas.attr("data-source"); plot_chart(canvas, url); @@ -14,13 +37,17 @@ $(document).ready(function () { }); function setlinks() { - $('.modal').modal(); - $('.dropdown-trigger').dropdown(); - $('.collapsible').collapsible(); - $("a.dashlink[data-target]").off('click').on('click', function(e) { + try { + M.Modal.init($('.modal'), {}); + M.Dropdown.init($('.dropdown-trigger'), {}); + M.Collapsible.init($('.collapsible'), {}); + } catch (e) { + console.log("A exception has ocurred", e) + } + $("a.dashlink[data-target]").off('click').on('click', function (e) { e.preventDefault(); var $this = $(this); - var target = $("#"+$this.attr('data-target')); + var target = $("#" + $this.attr('data-target')); var url = $this.attr('href'); if (target.is("canvas")) { plot_chart(target, url); @@ -32,18 +59,18 @@ function setlinks() { function get_content(container, url) { container.closest('.card').find('.full-preloader').removeClass('hide'); - $.get(url, function(data) { + $.get(url, function (data) { container.html(data); container.closest('.card').find('.full-preloader').addClass('hide'); setlinks(); - }).fail(function() { + }).fail(function () { container.closest('.card').find('.full-preloader').html("Ocorreu um erro. Tente recarregar a página"); }); } function plot_chart(canvas, url) { canvas.closest('.card').find('.full-preloader').removeClass('hide'); - $.get(url, function(data) { + $.get(url, function (data) { var chart_name = canvas.attr("data-chart-name"); var has_action_links = canvas.attr("data-has-action-links"); @@ -66,7 +93,7 @@ function plot_chart(canvas, url) { } setlinks(); canvas.closest('.card').find('.full-preloader').addClass('hide'); - }).fail(function() { + }).fail(function () { canvas.closest('.card').find('.full-preloader').html("Ocorreu um erro. Tente recarregar a página"); }); } \ No newline at end of file diff --git a/sigi/templates/material/admin/index.html b/sigi/templates/material/admin/index.html index 468fb13..1591526 100644 --- a/sigi/templates/material/admin/index.html +++ b/sigi/templates/material/admin/index.html @@ -11,8 +11,46 @@ {{ block.super }} {% endblock %} +{% block breadcrumbs %} +
+ +
+{% endblock %} + {% block content %} - {% include 'sigi/snippets/dashboard.html' %} + + {% for dash in sigi_dashes %} +
+ {% include 'sigi/snippets/dashboard.html' %} +
+ {% endfor %} {% endblock %} {% block footer %} diff --git a/sigi/templates/sigi/snippets/base_card.html b/sigi/templates/sigi/snippets/base_card.html index ec2743c..41ea39a 100644 --- a/sigi/templates/sigi/snippets/base_card.html +++ b/sigi/templates/sigi/snippets/base_card.html @@ -18,7 +18,11 @@
-
{% block card-title %}{% translate card_title|default:"" %}{% endblock %}
+
+ drag_handle + {% block card-title %}{% translate card_title|default:"" %}{% endblock %} + close +
{% block card-content %}{% endblock card-content %}
diff --git a/sigi/templates/sigi/snippets/dashboard.html b/sigi/templates/sigi/snippets/dashboard.html index 0a3646a..62e6e16 100644 --- a/sigi/templates/sigi/snippets/dashboard.html +++ b/sigi/templates/sigi/snippets/dashboard.html @@ -1,23 +1,55 @@ {% load i18n %} -
- {% url "home_resumoseit" as source_url %} - {% include "sigi/snippets/base_card_text.html" with card_title="Serviços hospedados no Interlegis (SEIT)" data_source=source_url card_name="resumoseit" %} - - {% url "home_chartseit" as source_url %} - {% include "sigi/snippets/base_card_chart.html" with card_title="Sazonalidade da hospedagem de serviços" data_source=source_url chart_name="evolucao-servicos" has_action_links="1" %} - - {% url "casas-carteira" as source_url %} - {% include "sigi/snippets/base_card_text.html" with card_title="Resumo da carteira de relacionamentos" data_source=source_url|add:"?snippet=resumo&s=sim" card_name="carteira" %} - - {% url "home_chartperformance" as source_url %} - {% include "sigi/snippets/base_card_chart.html" with card_title="Performance da gerência de carteiras" data_source=source_url chart_name="performance" has_action_links="1" %} - - {% url "home_chartcarteira" as source_url %} - {% include "sigi/snippets/base_card_chart.html" with card_title="Distribuição de Casas por Gerente" data_source=source_url chart_name="carteira" %} - - {% url "home_resumoconvenios" as source_url %} - {% include "sigi/snippets/base_card_text.html" with card_title="Resumo de informações" data_source=source_url card_name="resumo-convenios" %} +
+
+
{% csrf_token %} +
+ +
+ + +
+
+
+ +
+
+
+
+
    + {% for card in dash.cards %} +
  • + {% url card.nome_url as source_url %} + {% if card.tipo == 'C' %} + {% include "sigi/snippets/base_card_chart.html" with card_title=card.titulo data_source=source_url|add:"?"|add:card.query_string chart_name=card.codigo has_action_links=card.link_acao %} + {% else %} + {% include "sigi/snippets/base_card_text.html" with card_title=card.titulo data_source=source_url|add:"?"|add:card.query_string card_name=card.codigo %} + {% endif %} +
  • + {% endfor %} +
+
+
+
{% csrf_token %} +
+ +
    + {% for card in sigi_dash_all_cards %} +
  • + +
  • + {% endfor %} +
+
+
+ +
+
+
+