diff --git a/README.md b/README.md index 512c81cb0104f4c2be3d1c471b5a8855f53a24ea..236a10a5a206b8599318e639902da3002b8faad2 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,12 @@ gulp build ``` For more command see the `gulpfile.js` file. + + +# Running tests + +Run all tests for the project with the following command within the SSH session: + +``` bash +DJANGO_SETTINGS_MODULE=opentech.settings.test python manage.py test --keepdb +``` diff --git a/opentech/apply/funds/migrations/0050_roundsandlabs.py b/opentech/apply/funds/migrations/0050_roundsandlabs.py new file mode 100644 index 0000000000000000000000000000000000000000..c1f65610caac3d58ea047bf60072ee920f5d98c0 --- /dev/null +++ b/opentech/apply/funds/migrations/0050_roundsandlabs.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.9 on 2019-01-16 16:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0040_page_draft_title'), + ('funds', '0049_screening_status'), + ] + + operations = [ + migrations.CreateModel( + name='RoundsAndLabs', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/opentech/apply/funds/models/__init__.py b/opentech/apply/funds/models/__init__.py index 99f56509adf031f7eaab669918752fe5fa1ecc83..101a4b48011e5d342fcf8164a75a073020d4de32 100644 --- a/opentech/apply/funds/models/__init__.py +++ b/opentech/apply/funds/models/__init__.py @@ -1,6 +1,6 @@ from django.utils.translation import ugettext_lazy as _ -from .applications import ApplicationBase, RoundBase, LabBase +from .applications import ApplicationBase, RoundBase, LabBase, RoundsAndLabs # NOQA from .forms import ApplicationForm from .screening import ScreeningStatus from .submissions import ApplicationSubmission, ApplicationRevision diff --git a/opentech/apply/funds/models/applications.py b/opentech/apply/funds/models/applications.py index 0119e8c6503b93503336affddad338f2fd37ba3c..30097f1d3833a2c6f29beef9d29353bddf1c3c80 100644 --- a/opentech/apply/funds/models/applications.py +++ b/opentech/apply/funds/models/applications.py @@ -3,7 +3,21 @@ from datetime import date from django.conf import settings from django.core.exceptions import ValidationError from django.db import models -from django.db.models import OuterRef, Q, Subquery +from django.db.models import ( + Case, + CharField, + Count, + F, + FloatField, + Func, + IntegerField, + OuterRef, + Q, + Subquery, + When, +) +from django.db.models.functions import Coalesce, Length + from django.http import Http404 from django.utils.functional import cached_property from django.utils.text import mark_safe @@ -18,7 +32,7 @@ from wagtail.admin.edit_handlers import ( ObjectList, TabbedInterface, ) -from wagtail.core.models import PageManager, PageQuerySet +from wagtail.core.models import Page, PageManager, PageQuerySet from ..admin_forms import WorkflowFormAdminForm from ..edit_handlers import ReadOnlyPanel, ReadOnlyInlinePanel @@ -104,6 +118,11 @@ class RoundBaseManager(PageQuerySet): ) return rounds + def closed(self): + rounds = self.live().public().specific() + rounds = rounds.filter(end_date__lt=date.today()) + return rounds + class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore is_creatable = False @@ -314,3 +333,125 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm): # type: ig def open_round(self): return self.live + + +class RoundsAndLabsQueryset(PageQuerySet): + def new(self): + return self.filter(start_date__gt=date.today()) + + def open(self): + return self.filter(Q(end_date__gte=date.today(), start_date__lte=date.today()) | Q(end_date__isnull=True)) + + def closed(self): + return self.filter(end_date__lt=date.today()) + + +class RoundsAndLabsProgressQueryset(RoundsAndLabsQueryset): + def active(self): + return self.filter(progress__lt=100) + + def inactive(self): + return self.filter(progress=100) + + +class RoundsAndLabsManager(PageManager): + def get_queryset(self, base_queryset=RoundsAndLabsQueryset): + funds = ApplicationBase.objects.filter(path=OuterRef('parent_path')) + + return base_queryset(self.model, using=self._db).type(SubmittableStreamForm).annotate( + lead=Coalesce( + F('roundbase__lead__full_name'), + F('labbase__lead__full_name'), + ), + start_date=F('roundbase__start_date'), + end_date=F('roundbase__end_date'), + parent_path=Left(F('path'), Length('path') - ApplicationBase.steplen, output_field=CharField()), + fund=Subquery(funds.values('title')[:1]), + ) + + def with_progress(self): + submissions = ApplicationSubmission.objects.filter(Q(round=OuterRef('pk')) | Q(page=OuterRef('pk'))).current() + closed_submissions = submissions.inactive() + + return self.get_queryset(RoundsAndLabsProgressQueryset).annotate( + total_submissions=Coalesce( + Subquery( + submissions.values('round').annotate(count=Count('pk')).values('count'), + output_field=IntegerField(), + ), + 0, + ), + closed_submissions=Coalesce( + Subquery( + closed_submissions.values('round').annotate(count=Count('pk')).values('count'), + output_field=IntegerField(), + ), + 0, + ), + ).annotate( + progress=Case( + When(total_submissions=0, then=None), + default=(F('closed_submissions') * 100) / F('total_submissions'), + output_fields=FloatField(), + ) + + ) + + def open(self): + return self.get_queryset().open() + + def closed(self): + return self.get_queryset().closed() + + def new(self): + return self.get_queryset().new() + + +class RoundsAndLabs(Page): + """ + This behaves as a useful way to get all the rounds and labs that are defined + in the project regardless of how they are implemented (lab/round/sealed_round) + """ + class Meta: + proxy = True + + def __eq__(self, other): + # This is one way equality RoundAndLab == Round/Lab + # Round/Lab == RoundAndLab returns False due to different + # Concrete class + if not isinstance(other, models.Model): + return False + if not isinstance(other, SubmittableStreamForm): + return False + my_pk = self.pk + if my_pk is None: + return self is other + return my_pk == other.pk + + objects = RoundsAndLabsManager() + + def save(self, *args, **kwargs): + raise NotImplementedError('Do not save through this model') + + +# TODO remove in django 2.1 where this is fixed +F.relabeled_clone = lambda self, relabels: self + + +# TODO remove in django 2.1 where this is added +class Left(Func): + function = 'LEFT' + arity = 2 + + def __init__(self, expression, length, **extra): + """ + expression: the name of a field, or an expression returning a string + length: the number of characters to return from the start of the string + """ + if not hasattr(length, 'resolve_expression'): + if length < 1: + raise ValueError("'length' must be greater than 0.") + super().__init__(expression, length, **extra) + + def get_substr(self): + return Substr(self.source_expressions[0], Value(1), self.source_expressions[1]) diff --git a/opentech/apply/funds/tables.py b/opentech/apply/funds/tables.py index ff0561a2f6ae41944b149810b3128ad7e1f46720..fd5f5b5fd37e049efa843a86e22eb07877949aa8 100644 --- a/opentech/apply/funds/tables.py +++ b/opentech/apply/funds/tables.py @@ -1,4 +1,3 @@ -from datetime import date import textwrap from django import forms @@ -196,9 +195,9 @@ class ActiveRoundFilter(Select2MultipleChoiceFilter): value = value[0] if value == 'active': - return qs.filter(Q(progress__lt=100) | Q(progress__isnull=True)) + return qs.active() else: - return qs.filter(progress=100) + return qs.inactive() class OpenRoundFilter(Select2MultipleChoiceFilter): @@ -211,15 +210,15 @@ class OpenRoundFilter(Select2MultipleChoiceFilter): value = value[0] if value == 'closed': - return qs.filter(end_date__lt=date.today()) + return qs.closed() if value == 'new': - return qs.filter(start_date__gt=date.today()) + return qs.new() - return qs.filter(Q(end_date__gte=date.today(), start_date__lte=date.today()) | Q(end_date__isnull=True)) + return qs.open() class RoundsFilter(filters.FilterSet): fund = Select2ModelMultipleChoiceFilter(queryset=get_used_funds, label='Funds') lead = Select2ModelMultipleChoiceFilter(queryset=get_round_leads, label='Leads') active = ActiveRoundFilter(label='Active') - open_rouds = OpenRoundFilter(label='Open') + round_state = OpenRoundFilter(label='Open') diff --git a/opentech/apply/funds/templates/funds/base_submissions_table.html b/opentech/apply/funds/templates/funds/base_submissions_table.html index 0a620e2f6d1ab662dd126032491b9844c9ff812c..613466cda32e3520de28c79c4c00a6bdb4d17f6e 100644 --- a/opentech/apply/funds/templates/funds/base_submissions_table.html +++ b/opentech/apply/funds/templates/funds/base_submissions_table.html @@ -21,4 +21,5 @@ <script src="https://cdnjs.cloudflare.com/ajax/libs/url-search-params/1.1.0/url-search-params.js"></script> <script src="{% static 'js/apply/submission-filters.js' %}"></script> <script src="{% static 'js/apply/submission-tooltips.js' %}"></script> + <script src="{% static 'js/apply/tabs.js' %}"></script> {% endblock %} diff --git a/opentech/apply/funds/templates/funds/includes/round-block-listing.html b/opentech/apply/funds/templates/funds/includes/round-block-listing.html new file mode 100644 index 0000000000000000000000000000000000000000..4ae357bad00a87542372d6d390b87d543d110979 --- /dev/null +++ b/opentech/apply/funds/templates/funds/includes/round-block-listing.html @@ -0,0 +1,29 @@ +<ul class="round-block"> + {% for round in rounds %} + {% if forloop.counter0 < 5 %} + <li class="round-block__item"> + <h4 class="round-block__title">{{ round }}</h4> + <p> {{ round.fund|default_if_none:"-" }} </p> + <p class="round-block__date"> + {% if round.end_date %} + {{ display_text }} {{ round.end_date|date:"Y-m-d" }} + {% else %} + - + {% endif %} + </p> + <p class="round-block__determination"> + {% if round.progress is None%} + - + {% else %} + {{ round.progress }}% Determined + {% endif %} + </p> + <a class="round-block__view" href="{% url 'apply:rounds:detail' pk=round.pk %}">View</a> + </li> + {% else %} + <li class="round-block__item round-block__item--more"> + <a href="{% url 'apply:rounds:list' %}{{ query }}">Show all</a> + </li> + {% endif %} + {% endfor %} +</ul> diff --git a/opentech/apply/funds/templates/funds/includes/round-block.html b/opentech/apply/funds/templates/funds/includes/round-block.html new file mode 100644 index 0000000000000000000000000000000000000000..52e54fe78004e90f4111d2627415202a56d37e26 --- /dev/null +++ b/opentech/apply/funds/templates/funds/includes/round-block.html @@ -0,0 +1,20 @@ +<div class="wrapper wrapper--bottom-space"> + <section class="section section--with-options"> + <h4 class="heading heading--normal heading--no-margin">All Rounds and Labs</h4> + <div class="js-tabs"> + <a class="tab__item tab__item--alt" href="#closed-rounds" data-tab="tab-1">Closed</a> + <a class="tab__item tab__item--alt" href="#open-rounds" data-tab="tab-2">Open</a> + </div> + </section> + + {# Closed rounds/labs tab #} + <div class="tabs__content" id="tab-1"> + {% include "funds/includes/round-block-listing.html" with rounds=closed_rounds display_text="Closed" query=closed_query %} + </div> + + {# Open rounds/labs tab #} + <div class="tabs__content" id="tab-2"> + {% include "funds/includes/round-block-listing.html" with rounds=open_rounds display_text="Open until" query=open_query %} + </div> + +</div> diff --git a/opentech/apply/funds/templates/funds/submissions.html b/opentech/apply/funds/templates/funds/submissions.html index 4dcb9999f64620bc42c3b1d55c9390503c3e10bf..5589b26b28cab66cfed3fdff8db344fdd5eb613e 100644 --- a/opentech/apply/funds/templates/funds/submissions.html +++ b/opentech/apply/funds/templates/funds/submissions.html @@ -15,6 +15,9 @@ </div> <div class="wrapper wrapper--large wrapper--inner-space-medium"> + + {% include "funds/includes/round-block.html" with closed_rounds=closed_rounds open_rounds=open_rounds %} + {% block table %} {{ block.super }} {% endblock %} diff --git a/opentech/apply/funds/tests/factories/models.py b/opentech/apply/funds/tests/factories/models.py index 035f04115f6f050f67d6cb8ea6c3036dca65e883..6cc1e8165379c40dc229242e941bcb90c123e21d 100644 --- a/opentech/apply/funds/tests/factories/models.py +++ b/opentech/apply/funds/tests/factories/models.py @@ -136,6 +136,10 @@ class RoundFactory(wagtail_factories.PageFactory): start_date=factory.LazyFunction(datetime.date.today), end_date=factory.LazyFunction(lambda: datetime.date.today() + datetime.timedelta(days=7)), ) + closed = factory.Trait( + start_date=factory.LazyFunction(lambda: datetime.date.today() - datetime.timedelta(days=7)), + end_date=factory.LazyFunction(lambda: datetime.date.today() - datetime.timedelta(days=1)), + ) title = factory.Sequence('Round {}'.format) start_date = factory.Sequence(lambda n: datetime.date.today() + datetime.timedelta(days=7 * n + 1)) diff --git a/opentech/apply/funds/tests/models/__init__.py b/opentech/apply/funds/tests/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/apply/funds/tests/models/test_roundsandlabs.py b/opentech/apply/funds/tests/models/test_roundsandlabs.py new file mode 100644 index 0000000000000000000000000000000000000000..5eb8709d7b39e407a568deb51dd14ed9ff0ee27c --- /dev/null +++ b/opentech/apply/funds/tests/models/test_roundsandlabs.py @@ -0,0 +1,138 @@ +from django.test import TestCase + +from opentech.apply.funds.models import RoundsAndLabs + +from opentech.apply.funds.tests.factories import ( + ApplicationSubmissionFactory, + FundTypeFactory, + LabFactory, + LabSubmissionFactory, + RoundFactory, +) + + +class BaseRoundsAndLabTestCase: + def test_can_get(self): + obj = self.base_factory() + qs = RoundsAndLabs.objects.all() + self.assertEqual(qs.first(), obj) + + def test_with_progress(self): + obj = self.base_factory() + self.submission_factory(**{self.relation_to_app: obj}) + qs = RoundsAndLabs.objects.with_progress() + fetched_obj = qs.first() + self.assertEqual(fetched_obj.total_submissions, 1) + self.assertEqual(fetched_obj.closed_submissions, 0) + self.assertEqual(fetched_obj.progress, 0) + + def test_with_determined(self): + obj = self.base_factory() + self.submission_factory(**{self.relation_to_app: obj}, rejected=True) + qs = RoundsAndLabs.objects.with_progress() + fetched_obj = qs.first() + self.assertEqual(fetched_obj.total_submissions, 1) + self.assertEqual(fetched_obj.closed_submissions, 1) + self.assertEqual(fetched_obj.progress, 100) + + def test_annotated(self): + obj = self.base_factory() + qs = RoundsAndLabs.objects.with_progress() + fetched_obj = qs.first() + self.assertEqual(fetched_obj.lead, obj.lead.full_name) + self.assertEqual(fetched_obj.start_date, getattr(obj, 'start_date', None)) + self.assertEqual(fetched_obj.end_date, getattr(obj, 'end_date', None)) + self.assertEqual(fetched_obj.parent_path, obj.get_parent().path) + self.assertEqual(fetched_obj.fund, getattr(getattr(obj, 'fund', None), 'title', None)) + + def test_active(self): + obj = self.base_factory() + self.submission_factory(**{self.relation_to_app: obj}) + base_qs = RoundsAndLabs.objects.with_progress() + fetched_obj = base_qs.active().first() + self.assertEqual(fetched_obj, obj) + self.assertFalse(base_qs.inactive().exists()) + + def test_no_submissions_not_either(self): + self.base_factory() + base_qs = RoundsAndLabs.objects.with_progress() + self.assertFalse(base_qs.inactive().exists()) + self.assertFalse(base_qs.active().exists()) + + def test_inactive(self): + obj = self.base_factory() + self.submission_factory(**{self.relation_to_app: obj}, rejected=True) + base_qs = RoundsAndLabs.objects.with_progress() + fetched_obj = base_qs.inactive().first() + self.assertEqual(fetched_obj, obj) + self.assertFalse(base_qs.active().exists()) + + +class TestForLab(BaseRoundsAndLabTestCase, TestCase): + base_factory = LabFactory + submission_factory = LabSubmissionFactory + relation_to_app = 'page' + + # Specific tests as labs and round have very different behaviour here + def test_new(self): + self.base_factory() + fetched_obj = RoundsAndLabs.objects.new().first() + self.assertIsNone(fetched_obj) + + def test_closed(self): + self.base_factory() + fetched_obj = RoundsAndLabs.objects.closed().first() + self.assertIsNone(fetched_obj) + + def test_open(self): + obj = self.base_factory() + fetched_obj = RoundsAndLabs.objects.open().first() + self.assertEqual(fetched_obj, obj) + + +class TestForRound(BaseRoundsAndLabTestCase, TestCase): + base_factory = RoundFactory + submission_factory = ApplicationSubmissionFactory + relation_to_app = 'round' + + # Specific tests as labs and round have very different behaviour here + def test_new(self): + round = self.base_factory() + fetched_obj = RoundsAndLabs.objects.new().first() + self.assertEqual(fetched_obj, round) + + def test_closed(self): + round = self.base_factory(closed=True) + fetched_obj = RoundsAndLabs.objects.closed().first() + self.assertEqual(fetched_obj, round) + + def test_open(self): + obj = self.base_factory(now=True) + fetched_obj = RoundsAndLabs.objects.open().first() + self.assertEqual(fetched_obj, obj) + + +class TestRoundsAndLabsManager(TestCase): + def test_cant_get_fund(self): + FundTypeFactory() + qs = RoundsAndLabs.objects.all() + self.assertEqual(qs.count(), 0) + + def test_doesnt_confuse_lab_and_round(self): + round = RoundFactory() + lab = LabFactory() + + # Lab 50% progress + LabSubmissionFactory(page=lab) + LabSubmissionFactory(page=lab, rejected=True) + + # Round 0% progress + ApplicationSubmissionFactory.create_batch(2, round=round) + + fetched_lab = RoundsAndLabs.objects.with_progress().last() + fetched_round = RoundsAndLabs.objects.with_progress().first() + + self.assertEqual([fetched_round, fetched_lab], [round, lab]) + + self.assertEqual(fetched_round.progress, 0) + self.assertEqual(fetched_lab.progress, 50) diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index 1bc6e5e49dc40098efbed59b4a07607ac9d0736e..aa96782320842dba97f60c2ac789c29963b560e7 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -3,20 +3,7 @@ from copy import copy from django.contrib.auth.decorators import login_required from django.contrib import messages from django.core.exceptions import PermissionDenied -from django.db.models import ( - Case, - CharField, - Count, - F, - FloatField, - Func, - IntegerField, - OuterRef, - Q, - Subquery, - When, -) -from django.db.models.functions import Coalesce, Length +from django.db.models import Q from django.http import HttpResponseRedirect, Http404 from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy @@ -44,8 +31,7 @@ from opentech.apply.utils.views import DelegateableView, ViewDispatcher from .differ import compare from .forms import ProgressSubmissionForm, ScreeningSubmissionForm, UpdateReviewersForm, UpdateSubmissionLeadForm -from .models import ApplicationBase, ApplicationSubmission, ApplicationRevision, RoundBase, LabBase -from .models.utils import SubmittableStreamForm +from .models import ApplicationSubmission, ApplicationRevision, RoundsAndLabs, RoundBase, LabBase from .tables import ( AdminSubmissionsTable, RoundsTable, @@ -87,12 +73,27 @@ class BaseAdminSubmissionsTable(SingleTableMixin, FilterView): search_term=search_term, ) - return kwargs + return super().get_context_data(**kwargs) class SubmissionListView(AllActivityContextMixin, BaseAdminSubmissionsTable): template_name = 'funds/submissions.html' + def get_context_data(self, **kwargs): + base_query = RoundsAndLabs.objects.with_progress().active().order_by('-end_date') + open_rounds = base_query.open()[:6] + open_query = '?round_state=open&active=active' + closed_rounds = base_query.closed()[:6] + closed_query = '?round_state=closed&active=active' + + return super().get_context_data( + open_rounds=open_rounds, + open_query=open_query, + closed_rounds=closed_rounds, + closed_query=closed_query, + **kwargs, + ) + class SubmissionsByRound(BaseAdminSubmissionsTable): template_name = 'funds/submissions_by_round.html' @@ -491,63 +492,4 @@ class RoundListView(SingleTableMixin, FilterView): filterset_class = RoundsFilter def get_queryset(self): - submissions = ApplicationSubmission.objects.filter(Q(round=OuterRef('pk')) | Q(page=OuterRef('pk'))).current() - funds = ApplicationBase.objects.filter(path=OuterRef('parent_path')) - closed_submissions = submissions.inactive() - - queryset = Page.objects.type(SubmittableStreamForm).annotate( - total_submissions=Coalesce( - Subquery( - submissions.values('round').annotate(count=Count('pk')).values('count'), - output_field=IntegerField(), - ), - 0, - ), - closed_submissions=Coalesce( - Subquery( - closed_submissions.values('round').annotate(count=Count('pk')).values('count'), - output_field=IntegerField(), - ), - 0, - ), - lead=Coalesce( - F('roundbase__lead__full_name'), - F('labbase__lead__full_name'), - ), - start_date=F('roundbase__start_date'), - end_date=F('roundbase__end_date'), - parent_path=Left(F('path'), Length('path') - ApplicationBase.steplen, output_field=CharField()), - fund=Subquery(funds.values('title')[:1]), - ).annotate( - progress=Case( - When(total_submissions=0, then=None), - default=(F('closed_submissions') * 100) / F('total_submissions'), - output_fields=FloatField(), - ) - - ) - - return queryset - - -# TODO remove in django 2.1 where this is fixed -F.relabeled_clone = lambda self, relabels: self - - -# TODO remove in django 2.1 where this is added -class Left(Func): - function = 'LEFT' - arity = 2 - - def __init__(self, expression, length, **extra): - """ - expression: the name of a field, or an expression returning a string - length: the number of characters to return from the start of the string - """ - if not hasattr(length, 'resolve_expression'): - if length < 1: - raise ValueError("'length' must be greater than 0.") - super().__init__(expression, length, **extra) - - def get_substr(self): - return Substr(self.source_expressions[0], Value(1), self.source_expressions[1]) + return RoundsAndLabs.objects.with_progress() diff --git a/opentech/static_src/src/sass/apply/components/_round-block.scss b/opentech/static_src/src/sass/apply/components/_round-block.scss new file mode 100644 index 0000000000000000000000000000000000000000..a59dbc524fd0ecec584988da2e9abfbb6d0da339 --- /dev/null +++ b/opentech/static_src/src/sass/apply/components/_round-block.scss @@ -0,0 +1,75 @@ +.round-block { + $root: &; + + &__item { + align-items: center; + background-color: $color--white; + border: 1px solid $color--light-mid-grey; + border-bottom: 0; + padding: 25px; + transition: background-color $quick-transition; + min-height: 100px; + + @include media-query(tablet-landscape) { + display: flex; + + &:hover { + background-color: $color--light-grey; + border-right: 1px solid $color--light-grey; + border-left: 1px solid $color--light-grey; + } + + // item spacing + > * { + margin: 0 30px 0 0; + flex-basis: 200px; + } + } + + &--more { + padding: 20px 25px; + justify-content: center; + border-bottom: 1px solid $color--light-mid-grey; + min-height: auto; + + &:hover { + background-color: $color--white; + } + + // show more link + a { + margin: 0; + flex-basis: auto; + font-weight: $weight--semibold; + } + } + } + + &__view { + margin: 0 0 0 auto; + transition: color $quick-transition; + font-weight: $weight--bold; + text-transform: uppercase; + + @include media-query(tablet-landscape) { + color: transparent; + flex-basis: auto; + pointer-events: none; + } + + &:focus, + #{$root}__item:hover & { + color: $color--primary; + pointer-events: all; + } + } + + &__date, + &__determination { + color: $color--primary; + } + + &__title { + font-weight: $weight--semibold; + } +} diff --git a/opentech/static_src/src/sass/apply/components/_section.scss b/opentech/static_src/src/sass/apply/components/_section.scss index 4b7d3979b2b25d4725b21bb407eaec2334a63710..c7142f198c31cafdc2a42addce47023a00251b28 100644 --- a/opentech/static_src/src/sass/apply/components/_section.scss +++ b/opentech/static_src/src/sass/apply/components/_section.scss @@ -8,4 +8,15 @@ margin-bottom: 0; } } + + &--with-options { + display: flex; + flex-direction: column; + + @include media-query(small-tablet) { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + } } diff --git a/opentech/static_src/src/sass/apply/components/_tabs.scss b/opentech/static_src/src/sass/apply/components/_tabs.scss index 7bfc204205a2a8159a3cbd5bcfa792d5c0004a31..ca285d185d6256f6d2e6ef6db1bee4c6557083f5 100644 --- a/opentech/static_src/src/sass/apply/components/_tabs.scss +++ b/opentech/static_src/src/sass/apply/components/_tabs.scss @@ -11,11 +11,12 @@ &--current { display: inherit; } - } } .tab__item { + $root: &; + @include responsive-font-sizes(12px, 15px); position: relative; padding: 10px; @@ -39,6 +40,32 @@ color: $color--light-blue; } + &--alt { + font-size: map-get($font-sizes, zeta); + font-weight: $weight--semibold; + padding: 20px 10px; + text-transform: none; + display: inline-block; + margin-right: 20px; + border-bottom: 3px solid transparent; + color: $color--mid-dark-grey; + width: auto; + + &:hover { + color: $color--default; + } + + &#{$root}--active { + background-color: transparent; + border-bottom: 3px solid $color--primary; + + &:hover { + color: $color--default; + background-color: transparent; + } + } + } + &--active { color: $color--default; cursor: default; diff --git a/opentech/static_src/src/sass/apply/main.scss b/opentech/static_src/src/sass/apply/main.scss index 745f50807ad3b43d597b5a7f82021f9bec2de050..930863ffbb535a0ea37fe0f721fda96870bd1a00 100644 --- a/opentech/static_src/src/sass/apply/main.scss +++ b/opentech/static_src/src/sass/apply/main.scss @@ -37,6 +37,7 @@ @import 'components/reviews-list'; @import 'components/reviews-summary'; @import 'components/reviews-sidebar'; +@import 'components/round-block'; @import 'components/select2'; @import 'components/submission-meta'; @import 'components/revision';