From fea43f59932b0939761e49cc022821c0cc852937 Mon Sep 17 00:00:00 2001
From: sks444 <krishnasingh.ss30@gmail.com>
Date: Tue, 22 Sep 2020 17:26:40 +0530
Subject: [PATCH] Add filters to investments table

---
 hypha/public/partner/tables.py                |  65 ++++++-
 .../partner/base_investments_table.html       |  16 ++
 .../includes/table_filter_and_search.html     |  41 +++++
 hypha/public/partner/views.py                 |  21 ++-
 .../javascript/public/investment-filters.js   | 161 ++++++++++++++++++
 .../src/sass/public/abstracts/_variables.scss |   3 +
 .../sass/public/components/_actions-bar.scss  |  65 +++++++
 .../src/sass/public/components/_button.scss   |  49 ++++++
 .../src/sass/public/components/_filters.scss  |  63 +++++++
 .../src/sass/public/components/_form.scss     | 141 +++++++++++++++
 .../src/sass/public/components/_input.scss    |   8 +
 hypha/static_src/src/sass/public/main.scss    |   2 +
 12 files changed, 630 insertions(+), 5 deletions(-)
 create mode 100644 hypha/public/partner/templates/partner/includes/table_filter_and_search.html
 create mode 100644 hypha/static_src/src/javascript/public/investment-filters.js
 create mode 100644 hypha/static_src/src/sass/public/components/_actions-bar.scss
 create mode 100644 hypha/static_src/src/sass/public/components/_filters.scss

diff --git a/hypha/public/partner/tables.py b/hypha/public/partner/tables.py
index 066ff5ff6..3be0a5e08 100644
--- a/hypha/public/partner/tables.py
+++ b/hypha/public/partner/tables.py
@@ -1,7 +1,70 @@
 import django_tables2 as tables
 from django.utils.translation import gettext_lazy as _
+import django_filters as filters
+from django import forms
+from django.db.models import Q
+from hypha.apply.funds.tables import Select2MultipleChoiceFilter
+from .models import Investment, InvestmentCategorySettings, PartnerPage
 
-from .models import Investment, InvestmentCategorySettings
+
+def get_year_choices():
+    years = Investment.objects.order_by('-year').values_list('year', flat=True).distinct()
+    return [(year, str(year)) for year in years]
+
+
+class InvestmentFilter(filters.FilterSet):
+    PAGE_CHOICES = (
+        (25, '25'),
+        (50, '50'),
+        (100, '100'),
+    )
+
+    AMOUNT_COMMITTED_CHOICES = (
+        ('0_250k', '$0 > $250k'),
+        ('250k_1m', '$250k > $1m'),
+        ('1m+', '$1m+'),
+    )
+
+    YEAR_CHOICES = get_year_choices()
+
+    year = Select2MultipleChoiceFilter(choices=YEAR_CHOICES, label='Years')
+    amount_committed = Select2MultipleChoiceFilter(
+        choices=AMOUNT_COMMITTED_CHOICES,
+        label='Amount Committed(US$)',
+        method='filter_amount_committed'
+    )
+    partner__status = Select2MultipleChoiceFilter(
+        choices=PartnerPage.STATUS, label='Status'
+    )
+    per_page = filters.ChoiceFilter(
+        choices=PAGE_CHOICES,
+        empty_label=_('Items per page'),
+        label='Per page',
+        method='per_page_handler'
+    )
+
+    class Meta:
+        model = Investment
+        fields = ('year', 'amount_committed')
+
+    def filter_amount_committed(self, queryset, name, value):
+        query = Q()
+        for v in value:
+            if v == '0_250k':
+                query |= Q(amount_committed__gte=0, amount_committed__lt=250000)
+            if v == '250k_1m':
+                query |= Q(amount_committed__gte=250000, amount_committed__lt=1000000)
+            if v == '1m+':
+                query |= Q(amount_committed__gte=1000000)
+        return queryset.filter(query)
+
+    def per_page_handler(self, queryset, name, value):
+        # Pagination is already implemented in view. We only need to add per_page query parameter.
+        return queryset
+
+
+class InvestmentFilterAndSearch(InvestmentFilter):
+    query = filters.CharFilter(field_name='search_data', lookup_expr="icontains", widget=forms.HiddenInput)
 
 
 class InvestmentTable(tables.Table):
diff --git a/hypha/public/partner/templates/partner/base_investments_table.html b/hypha/public/partner/templates/partner/base_investments_table.html
index 71d970299..a89fadd02 100644
--- a/hypha/public/partner/templates/partner/base_investments_table.html
+++ b/hypha/public/partner/templates/partner/base_investments_table.html
@@ -3,8 +3,24 @@
 {% load static %}
 {% load render_table from django_tables2 %}
 
+{% block extra_css %}
+<link rel="stylesheet" href="{% static 'css/apply/fancybox.css' %}">
+{{ filter.form.media.css }}
+{% endblock %}
+
 {% block content %}
     {% block table %}
+        {% include "partner/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action %}
+
         {% render_table table %}
     {% endblock %}
 {% endblock %}
+
+{% block extra_js %}
+    {{ filter.form.media.js }}
+    <script src="//cdnjs.cloudflare.com/ajax/libs/fancybox/3.4.1/jquery.fancybox.min.js"></script>
+    <script src="{% static 'js/apply/fancybox-global.js' %}"></script>
+    <script src="https://cdn.jsdelivr.net/npm/symbol-es6@0.1.2/symbol-es6.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/url-search-params/1.1.0/url-search-params.js"></script>
+    <script src="{% static 'js/public/investment-filters.js' %}"></script>
+{% endblock %}
diff --git a/hypha/public/partner/templates/partner/includes/table_filter_and_search.html b/hypha/public/partner/templates/partner/includes/table_filter_and_search.html
new file mode 100644
index 000000000..8a8558d9e
--- /dev/null
+++ b/hypha/public/partner/templates/partner/includes/table_filter_and_search.html
@@ -0,0 +1,41 @@
+<div class="wrapper wrapper--table-actions js-table-actions">
+    <div class="actions-bar">
+        {# Left #}
+        <div class="actions-bar__inner actions-bar__inner--left">
+            {% if heading %}
+                <h4 class="heading heading--normal heading--no-margin">{{ heading }}</h4>
+            {% endif %}
+        </div>
+
+        {# Right #}
+        <div class="actions-bar__inner actions-bar__inner--right">
+            <button class="button button--filters js-toggle-filters">Filters</button>
+
+            {% if use_search|default:False %}
+            <form action="{{ search_action }}" method="get" role="search" class="form form--search-desktop">
+                <button class="button button--search" type="submit" aria-label="Search">
+                    <svg class="icon icon--magnifying-glass icon--search"><use xlink:href="#magnifying-glass"></use></svg>
+                </button>
+                <input class="input input--search input--secondary" type="text" placeholder="Search {{ search_placeholder|default:"investments" }}" name="query"{% if search_term %} value="{{ search_term }}"{% endif %} aria-label="Search input">
+            </form>
+            {% endif %}
+        </div>
+    </div>
+</div>
+
+<div class="filters {% if filter_classes %}{{filter_classes}}{% endif %}">
+    <div class="filters__header">
+        <button class="filters__button js-clear-filters">Clear</button>
+        <div>Filter by</div>
+        <button class="filters__button js-close-filters">Close</button>
+    </div>
+
+    <form action="{{ filter_action }}" method="get" class="form form--filters js-filter-form">
+        <ul class="form__filters select2">
+            {{ filter.form.as_ul }}
+            <li>
+                <button class="button button--primary" type="submit" value="Filter">Filter</button>
+            </li>
+        </ul>
+    </form>
+</div>
diff --git a/hypha/public/partner/views.py b/hypha/public/partner/views.py
index 3cff8ecf0..b39ae699c 100644
--- a/hypha/public/partner/views.py
+++ b/hypha/public/partner/views.py
@@ -1,12 +1,16 @@
-from django_tables2 import SingleTableView
-
+from django_filters.views import FilterView
 from .models import Investment
-from .tables import InvestmentTable
+from .tables import InvestmentTable, InvestmentFilterAndSearch, InvestmentFilter
+from django_tables2.views import SingleTableMixin
+from django_tables2.paginators import LazyPaginator
 
 
-class InvestmentTableView(SingleTableView):
+class InvestmentTableView(SingleTableMixin, FilterView):
     model = Investment
     table_class = InvestmentTable
+    filterset_class = InvestmentFilterAndSearch
+    filter_action = ''
+    paginator_class = LazyPaginator
     table_pagination = {'per_page': 25}
     template_name = 'partner/investments.html'
 
@@ -14,3 +18,12 @@ class InvestmentTableView(SingleTableView):
         kwargs = super(InvestmentTableView, self).get_table_kwargs()
         kwargs['request'] = self.request
         return kwargs
+
+    def get_context_data(self, **kwargs):
+        search_term = self.request.GET.get('query')
+
+        return super().get_context_data(
+            search_term=search_term,
+            filter_action=self.filter_action,
+            **kwargs,
+        )
diff --git a/hypha/static_src/src/javascript/public/investment-filters.js b/hypha/static_src/src/javascript/public/investment-filters.js
new file mode 100644
index 000000000..dcd8bff44
--- /dev/null
+++ b/hypha/static_src/src/javascript/public/investment-filters.js
@@ -0,0 +1,161 @@
+(function ($) {
+
+    'use strict';
+
+    // Variables
+    const $toggleButton = $('.js-toggle-filters');
+    const $closeButton = $('.js-close-filters');
+    const $clearButton = $('.js-clear-filters');
+    const filterOpenClass = 'filters-open';
+    const filterActiveClass = 'is-active';
+
+    const urlParams = new URLSearchParams(window.location.search);
+
+    const persistedParams = ['sort', 'query', 'investment'];
+
+    // check if the page has a query string and keep filters open if so on desktop
+    const minimumNumberParams = persistedParams.reduce(
+        (count, param) => count + urlParams.has(param) ? 1 : 0,
+        0
+    );
+
+    if ([...urlParams].length > minimumNumberParams && $(window).width() > 1024) {
+        $('.filters').addClass(filterOpenClass);
+        $('.js-toggle-filters').text('Clear filters');
+    }
+
+    // Add active class to filters - dropdowns are dynamically appended to the dom,
+    // so we have to listen for the event higher up
+    $('body').on('click', '.select2-dropdown', (e) => {
+        // get the id of the dropdown
+        let selectId = e.target.parentElement.parentElement.id;
+
+        // find the matching dropdown
+        let match = $(`.select2-selection[aria-owns="${selectId}"]`);
+
+        // if the dropdown contains a clear class, the filters are active
+        if ($(match[0]).find('span.select2-selection__clear').length !== 0) {
+            match[0].classList.add(filterActiveClass);
+        }
+        else {
+            match[0].classList.remove(filterActiveClass);
+        }
+    });
+
+    // remove active class on clearing select2
+    $('.select2').on('select2:unselecting', (e) => {
+        const dropdown = e.target.nextElementSibling.firstChild.firstChild;
+        if (dropdown.classList.contains(filterActiveClass)) {
+            dropdown.classList.remove(filterActiveClass);
+        }
+    });
+
+    // toggle filters
+    $toggleButton.on('click', (e) => {
+        // find the nearest filters
+        const filters = e.target.closest('.js-table-actions').nextElementSibling;
+
+        if (filters.classList.contains(filterOpenClass)) {
+            handleClearFilters();
+        }
+        else {
+            filters.classList.add(filterOpenClass);
+            // only update button text on desktop
+            if (window.innerWidth >= 1024) {
+                updateButtonText(e.target, filters);
+            }
+        }
+    });
+
+    // close filters on mobile
+    $closeButton.on('click', (e) => {
+        e.target.closest('.filters').classList.remove(filterOpenClass);
+    });
+
+    // redirect to investments home to clear filters
+    function handleClearFilters() {
+        const query = persistedParams.reduce(
+            (query, param) => query + (urlParams.get(param) ? `&${param}=${urlParams.get(param)}` : ''), '?');
+        window.location.href = window.location.href.split('?')[0] + query;
+    }
+
+    // toggle filters button wording
+    function updateButtonText(button, filters) {
+        if (filters.classList.contains(filterOpenClass)) {
+            button.textContent = 'Clear filters';
+        }
+        else {
+            button.textContent = 'Filters';
+        }
+    }
+
+    // corrects spacing of dropdowns when toggled on mobile
+    function mobileFilterPadding(element) {
+        const expanded = 'expanded-filter-element';
+        const dropdown = $(element).closest('.select2');
+        const openDropdown = $('.select2 .' + expanded);
+        let dropdownMargin = 0;
+
+        if (openDropdown.length > 0 && !openDropdown.hasClass('select2-container--open')) {
+            // reset the margin of the select we previously worked
+            openDropdown.removeClass(expanded);
+            // store the offset to adjust the new select box (elements above the old dropdown unaffected)
+            if (dropdown.position().top > openDropdown.position().top) {
+                dropdownMargin = parseInt(openDropdown.css('marginBottom'));
+            }
+            openDropdown.css('margin-bottom', '0px');
+        }
+
+        if (dropdown.hasClass('select2-container--open')) {
+            dropdown.addClass(expanded);
+            const dropdownID = $(element).closest('.select2-selection').attr('aria-owns');
+            // Element which has the height of the select dropdown
+            const match = $(`ul#${dropdownID}`);
+            const dropdownHeight = match.outerHeight(true);
+
+            // Element which has the position of the dropdown
+            const positionalMatch = match.closest('.select2-container');
+
+            // Pad the bottom of the select box
+            dropdown.css('margin-bottom', `${dropdownHeight}px`);
+
+            // bump up the dropdown options by height of closed elements
+            positionalMatch.css('top', positionalMatch.position().top - dropdownMargin);
+        }
+    }
+
+    // clear all filters
+    $clearButton.on('click', () => {
+        const dropdowns = document.querySelectorAll('.form__filters select');
+        dropdowns.forEach(dropdown => {
+            $(dropdown).val(null).trigger('change');
+            $('.select2-selection.is-active').removeClass(filterActiveClass);
+            mobileFilterPadding(dropdown); // eslint-disable-line no-undef
+        });
+    });
+
+    $(function () {
+        // Add active class to select2 checkboxes after page has been filtered
+        const clearButtons = document.querySelectorAll('.select2-selection__clear');
+        clearButtons.forEach(clearButton => {
+            clearButton.parentElement.parentElement.classList.add(filterActiveClass);
+        });
+    });
+
+    // reset mobile filters if they're open past the tablet breakpoint
+    $(window).resize(function resize() {
+        if ($(window).width() < 1024) {
+            // close the filters if open when reducing the window size
+            $('body').removeClass('filters-open');
+
+            // update filter button text
+            $('.js-toggle-filters').text('Filters');
+
+            // Correct spacing of dropdowns when toggled
+            $('.select2').on('click', (e) => {
+                mobileFilterPadding(e.target);
+            });
+        }
+    }).trigger('resize');
+
+})(jQuery);
diff --git a/hypha/static_src/src/sass/public/abstracts/_variables.scss b/hypha/static_src/src/sass/public/abstracts/_variables.scss
index 4eba93625..6166355b3 100644
--- a/hypha/static_src/src/sass/public/abstracts/_variables.scss
+++ b/hypha/static_src/src/sass/public/abstracts/_variables.scss
@@ -111,6 +111,9 @@ $transition: .25s ease-out;
 $quick-transition: .15s ease;
 $medium-transition: .5s ease;
 
+// Filters
+$filter-dropdown: '.select2 .select2-selection.select2-selection--single';
+
 // Delays
 $base-delay: 30ms;
 
diff --git a/hypha/static_src/src/sass/public/components/_actions-bar.scss b/hypha/static_src/src/sass/public/components/_actions-bar.scss
new file mode 100644
index 000000000..f2afad48a
--- /dev/null
+++ b/hypha/static_src/src/sass/public/components/_actions-bar.scss
@@ -0,0 +1,65 @@
+.actions-bar {
+    margin: 20px 0;
+    width: 100%;
+
+    @include media-query(tablet-landscape) {
+        display: flex;
+        justify-content: space-between;
+    }
+
+    &__inner {
+        & > * {
+            margin-bottom: 20px;
+        }
+
+        @include media-query(tablet-landscape) {
+            display: flex;
+            align-items: center;
+
+            & > * {
+                margin: 0 0 0 20px;
+
+                &:first-child {
+                    margin-left: 0;
+                }
+            }
+        }
+
+        &--left {
+            display: flex;
+            flex-direction: column;
+            align-items: flex-start;
+        }
+
+        &--right {
+            align-items: flex-end;
+        }
+
+        &--batch-actions {
+            display: none;
+
+            @include media-query(tablet-landscape) {
+                display: flex;
+                opacity: 0;
+                pointer-events: none;
+                transition: opacity $quick-transition;
+                margin: 20px 0 0;
+
+                .batch-actions-enabled & {
+                    opacity: 1;
+                    pointer-events: all;
+                }
+            }
+        }
+    }
+
+    &__total {
+        background-color: $color--light-blue;
+        color: $color--white;
+        padding: 6px 16px;
+        border-radius: 30px;
+        min-width: 120px;
+        text-align: center;
+        font-weight: $weight--semibold;
+    }
+}
diff --git a/hypha/static_src/src/sass/public/components/_button.scss b/hypha/static_src/src/sass/public/components/_button.scss
index a3b07c919..836ed57cd 100644
--- a/hypha/static_src/src/sass/public/components/_button.scss
+++ b/hypha/static_src/src/sass/public/components/_button.scss
@@ -26,6 +26,55 @@
         }
     }
 
+    &--filters {
+        display: flex;
+        justify-content: space-between;
+        max-width: 300px;
+        padding: 15px 20px;
+        font-weight: $weight--normal;
+        color: $color--default;
+        background: url('./../../images/filters.svg') $color--white no-repeat 93% center;
+        border: 1px solid $color--light-mid-grey;
+        transition: none;
+        width: 100%;
+
+        @include media-query(tablet-landscape) {
+            background: none;
+            padding: 10px;
+            border: 0;
+            justify-content: flex-start;
+            width: auto;
+            opacity: .7;
+
+            &::before {
+                content: '';
+                background-image: url('./../../images/filters.svg');
+                transform: rotate(90deg);
+                background-color: transparent;
+                background-position: left center;
+                background-size: 20px;
+                width: 20px;
+                height: 20px;
+                display: inline-block;
+                margin-right: 10px;
+            }
+        }
+    }
+
+    &--filters-header {
+        display: flex;
+    }
+
+    &--search {
+        position: absolute;
+        top: .65em;
+        right: 10px;
+
+        svg {
+            fill: $color--primary;
+        }
+    }
+
     &--left-space {
         margin-left: 20px;
     }
diff --git a/hypha/static_src/src/sass/public/components/_filters.scss b/hypha/static_src/src/sass/public/components/_filters.scss
new file mode 100644
index 000000000..9a7f427b9
--- /dev/null
+++ b/hypha/static_src/src/sass/public/components/_filters.scss
@@ -0,0 +1,63 @@
+.filters {
+    display: none;
+
+    &.filters-open {
+        position: fixed;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        left: 0;
+        z-index: 20;
+        display: block;
+        width: 100%;
+        height: 100vh;
+        background: $color--white;
+    }
+
+    @include media-query(tablet-landscape) {
+        display: block;
+        max-height: 0;
+        transition: max-height $medium-transition;
+        transition-delay: $base-delay;
+        pointer-events: none;
+
+        &.filters-open {
+            position: relative;
+            top: auto;
+            left: auto;
+            right: auto;
+            bottom: auto;
+            height: auto;
+            background: transparent;
+            max-height: 85px;
+            pointer-events: all;
+        }
+    }
+
+    &__header {
+        display: flex;
+        align-items: center;
+        justify-content: space-around;
+        padding: 20px 0;
+
+        @include media-query(tablet-landscape) {
+            display: none;
+        }
+
+        > div[class^='js-'] {
+            color: $color--primary;
+
+            &:hover {
+                cursor: pointer;
+            }
+        }
+    }
+
+    &__button {
+        appearance: none;
+        -webkit-appearance: none; // sass-lint:disable-line no-vendor-prefixes
+        border: 0;
+        color: $color--primary;
+        background: transparent;
+    }
+}
diff --git a/hypha/static_src/src/sass/public/components/_form.scss b/hypha/static_src/src/sass/public/components/_form.scss
index f2a5c7e56..5f2859bde 100644
--- a/hypha/static_src/src/sass/public/components/_form.scss
+++ b/hypha/static_src/src/sass/public/components/_form.scss
@@ -32,6 +32,17 @@
         }
     }
 
+    &--search-desktop {
+        position: relative;
+        max-width: 300px;
+        margin-top: $mobile-gutter;
+
+        @include media-query(tablet-landscape) {
+            max-width: 280px;
+            margin: 0 0 0 30px;
+        }
+    }
+
     &__group {
         position: relative;
         margin: 1rem 0;
@@ -150,6 +161,124 @@
         border-radius: 5px;
     }
 
+    &__filters {
+        #{$filter-dropdown} {
+            border: 0;
+            border-top: 1px solid $color--mid-grey;
+
+            &.is-active {
+                font-weight: $weight--normal;
+                background-color: transparentize($color--primary, .9);
+                border-color: $color--mid-grey;
+            }
+
+            @include media-query(tablet-landscape) {
+                border: 1px solid $color--mid-grey;
+            }
+        }
+
+        @include media-query(tablet-landscape) {
+            display: flex;
+            align-items: flex-start;
+            padding: 10px 0 30px;
+            opacity: 0;
+            transition: opacity $transition;
+
+            .filters-open & {
+                opacity: 1;
+                transition-delay: $base-delay * 10;
+            }
+
+            .filters--dates & {
+                align-items: flex-end;
+                margin: 10px 0 30px;
+                padding: 0;
+            }
+        }
+
+        label {
+            display: none;
+
+            .filters--dates & {
+                display: block;
+            }
+        }
+
+        // so the form can be output in any tag
+        > * {
+            @include media-query(tablet-landscape) {
+                flex-basis: 225px;
+
+                &:not(:last-child) {
+                    margin-right: 10px;
+                }
+            }
+        }
+
+        &--mobile {
+            flex-direction: column;
+            padding: 0;
+
+            // so the form can be output in any tag
+            > * {
+                flex-basis: auto;
+                margin: 0;
+            }
+        }
+
+        > li {
+            padding: 0 1rem;
+
+            @include media-query(tablet-landscape) {
+                padding: 0;
+            }
+
+            // re-order from/to date inputs and text
+            .filters--dates & {
+                margin: 0 auto 1rem;
+                max-width: 320px;
+
+                @include media-query(mob-landscape) {
+                    display: flex;
+                    max-width: 600px;
+
+                    @supports (display: grid) {
+                        display: grid;
+                        grid-template-columns: 1fr 1fr;
+                        grid-gap: 5px;
+                    }
+                }
+
+                @include media-query(tablet-landscape) {
+                    margin: 0 1rem 0 0;
+                    max-width: initial;
+                }
+
+                label {
+                    @supports (display: grid) {
+                        grid-column: 1;
+                        grid-row: 1;
+                    }
+                }
+
+                input {
+                    &:first-of-type {
+                        @supports (display: grid) {
+                            grid-column: 1;
+                        }
+                    }
+                }
+
+                span {
+                    @supports (display: grid) {
+                        grid-column: 2;
+                        grid-row: 1;
+                    }
+                }
+            }
+        }
+    }
+
     &__required {
         color: $color--purple;
     }
@@ -176,6 +305,18 @@
         background: url('./../../images/dropdown.svg') $color--white no-repeat 95% center;
         background-size: 8px;
 
+        .form--scoreable & {
+            margin-top: 20px;
+        }
+
+        .form__filters & {
+            max-width: 100%;
+
+            select {
+                height: $dropdown-height;
+            }
+        }
+
         select[multiple='multiple'] {
             display: block;
         }
diff --git a/hypha/static_src/src/sass/public/components/_input.scss b/hypha/static_src/src/sass/public/components/_input.scss
index eaaf2ee57..bbd6e1cd5 100644
--- a/hypha/static_src/src/sass/public/components/_input.scss
+++ b/hypha/static_src/src/sass/public/components/_input.scss
@@ -8,4 +8,12 @@
     &--bottom-space {
         margin-bottom: 10px;
     }
+
+    &--search {
+        width: 100%;
+        padding: 10px;
+        color: $color--dark-grey;
+        background: transparent;
+        border: 1px solid $color--mid-dark-grey;
+    }
 }
diff --git a/hypha/static_src/src/sass/public/main.scss b/hypha/static_src/src/sass/public/main.scss
index 4c56f2abf..91bb5539e 100644
--- a/hypha/static_src/src/sass/public/main.scss
+++ b/hypha/static_src/src/sass/public/main.scss
@@ -10,6 +10,7 @@
 // Components
 @import 'components/apply-bar';
 @import 'components/button';
+@import 'components/actions-bar';
 @import 'components/blockquote';
 @import 'components/editor';
 @import 'components/card';
@@ -38,6 +39,7 @@
 @import 'components/select2';
 @import 'components/show-more';
 @import 'components/table';
+@import 'components/filters';
 @import 'components/wrapper';
 
 // Layout
-- 
GitLab