Skip to content

Commit

Permalink
feature: date time filter (#3132)
Browse files Browse the repository at this point in the history
* feature: date time filter

* lint

* use button instead submit on close

* tweaks and tests

* lint & tweaks

* tweak
  • Loading branch information
Paul-Bob authored Aug 20, 2024
1 parent d75efb9 commit b40b6b7
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 4 deletions.
2 changes: 2 additions & 0 deletions app/javascript/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CodeFieldController from './controllers/fields/code_field_controller'
import CopyToClipboardController from './controllers/copy_to_clipboard_controller'
import DashboardCardController from './controllers/dashboard_card_controller'
import DateFieldController from './controllers/fields/date_field_controller'
import DateTimeFilterController from './controllers/date_time_filter_controller'
import EasyMdeController from './controllers/fields/easy_mde_controller'
import FilterController from './controllers/filter_controller'
import HiddenInputController from './controllers/hidden_input_controller'
Expand Down Expand Up @@ -53,6 +54,7 @@ application.register('boolean-filter', BooleanFilterController)
application.register('card-filters', CardFiltersController)
application.register('copy-to-clipboard', CopyToClipboardController)
application.register('dashboard-card', DashboardCardController)
application.register('date-time-filter', DateTimeFilterController)
application.register('filter', FilterController)
application.register('panel-refresh', PanelRefreshController)
application.register('hidden-input', HiddenInputController)
Expand Down
32 changes: 32 additions & 0 deletions app/javascript/js/controllers/date_time_filter_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import flatpickr from 'flatpickr'

import BaseFilterController from './filter_controller'

export default class extends BaseFilterController {
static targets = ['input']

static values = {
class: String,
pickerOptions: Object,
}

getFilterValue() {
return this.inputTarget.value
}

getFilterClass() {
return this.classValue
}

connect() {
this.initFlatpickr()
}

initFlatpickr() {
this.pickerInstance = flatpickr(this.inputTarget, this.pickerOptionsValue)
}

clear() {
this.inputTarget._flatpickr.clear()
}
}
37 changes: 37 additions & 0 deletions app/views/avo/base/_date_time_filter.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<%= content_tag :div, data: {
controller: "date-time-filter",
filter_name: filter.name,
date_time_filter_class_value: filter.class.to_s,
date_time_filter_keep_filters_panel_open_value: @resource.keep_filters_panel_open,
date_time_filter_picker_options_value: filter.picker_options(@applied_filters[filter.class.to_s])
} do %>
<%= filter_wrapper name: filter.name do %>
<div class="flex relative">
<div class="self-center w-full" data-slot="value">
<%= text_field_tag :value, "",
class: input_classes('w-full mb-0 !pr-9'),
placeholder: filter.class.empty_message,
data: {
date_time_filter_target: "input",
} %>
</div>
<%= content_tag :button,
class: "absolute right-0 self-center mr-4 uppercase font-semibold text-xs",
id: :reset,
type: :button,
title: t("avo.reset").capitalize,
data: {
action: "click->date-time-filter#clear",
tippy: :tooltip
} do %>
<%= svg "avo/times", class: "h-4" %>
<% end %>
</div>
<div class="flex justify-end">
<%= a_button class: 'mt-4', color: :primary, data: { action: "date-time-filter#changeFilter" }, size: :xs do %>
<%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
<% end %>
</div>
<%= link_to 'url_redirect', request.url, data: { 'date-time-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, style: 'hidden', class: 'hidden' %>
<% end %>
<% end %>
2 changes: 1 addition & 1 deletion app/views/avo/base/_multiple_select_filter.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
%>
<div class="flex justify-end">
<%= a_button class: 'mt-4', color: :primary, size: :xs, data: { action: "multiple-select-filter#changeFilter" } do %>
<%= "#{t("avo.filter_by")} #{filter.name}" %>
<%= filter.button_label || "#{t("avo.filter_by")} #{filter.name}" %>
<% end %>
</div>
<%= link_to 'url_redirect', request.url, data: { 'multiple-select-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, style: 'hidden', class: 'hidden' %>
Expand Down
1 change: 1 addition & 0 deletions lib/avo/filters/base_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class BaseFilter
class_attribute :name, default: "Filter"
class_attribute :template, default: "avo/base/select_filter"
class_attribute :visible
class_attribute :button_label

attr_reader :arguments

Expand Down
40 changes: 40 additions & 0 deletions lib/avo/filters/date_time_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module Avo
module Filters
class DateTimeFilter < BaseFilter
class_attribute :type, default: :date_time
class_attribute :mode, default: :range

self.template = "avo/base/date_time_filter"

def picker_format
case type
when :date
"Y-m-d"
when :date_time
"Y-m-d H:i:S"
when :time
"H:i:S"
end
end

def picker_options(value)
{
defaultDate: value,
enableTime: has_time?,
enableSeconds: has_time?,
time_24hr: has_time? ? true : nil,
noCalendar: type == :time,
mode: mode,
dateFormat: picker_format,
minuteIncrement: has_time? ? 1 : nil
}.compact
end

def has_time?
@has_time ||= type.in?([:time, :date_time])
end
end
end
end
2 changes: 0 additions & 2 deletions lib/avo/filters/text_filter.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module Avo
module Filters
class TextFilter < BaseFilter
class_attribute :button_label

self.template = "avo/base/text_filter"

def selected_value(applied_filters)
Expand Down
2 changes: 1 addition & 1 deletion lib/generators/avo/filter_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def create_resource_file
private

def filter_types
%w[boolean select text multiple_select]
%w[boolean select text multiple_select date_time]
end
end
end
Expand Down
23 changes: 23 additions & 0 deletions lib/generators/avo/templates/filters/date_time_filter.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

class Avo::Filters::<%= class_name.camelize %> < Avo::Filters::DateTimeFilter
self.name = "<%= name.underscore.humanize %>"
# self.type = :date_time
# self.mode = :range
# self.visible = -> do
# true
# end

def apply(request, query, value)
query
end

# def picker_format
# case type
# when :date_time
# "Y-m-d H:i:S"
# when :time
# "H:i:S"
# end
# end
end
14 changes: 14 additions & 0 deletions spec/dummy/app/avo/filters/birthday.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class Avo::Filters::Birthday < Avo::Filters::DateTimeFilter
self.name = "Birthday"
self.button_label = "Apply birthday filter"
self.empty_message = "Filter by birthday"
self.type = :date

def apply(request, query, value)
start_date, end_date = value.split(" to ")

query.where(birthday: start_date..end_date)
end
end
13 changes: 13 additions & 0 deletions spec/dummy/app/avo/filters/starting_at.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class Avo::Filters::StartingAt < Avo::Filters::DateTimeFilter
self.name = "The starting at filter"
self.button_label = "Filter by start time"
self.empty_message = "Search by start time"
self.type = :time
self.mode = :single

def apply(request, query, value)
query.where("to_char(starting_at, 'HH24:MI:SS') = ?", value)
end
end
1 change: 1 addition & 0 deletions spec/dummy/app/avo/resources/course.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def fields_bag
def filters
filter Avo::Filters::CourseCountryFilter
filter Avo::Filters::CourseCityFilter
filter Avo::Filters::StartingAt
end

def actions
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/avo/resources/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def actions
def filters
filter Avo::Filters::UserNamesFilter
filter Avo::Filters::IsAdmin
filter Avo::Filters::Birthday
filter Avo::Filters::DummyMultipleSelectFilter
end

Expand Down
82 changes: 82 additions & 0 deletions spec/system/avo/filters/filters_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -350,4 +350,86 @@
end
end
end

describe "date time filters" do
context "date with range selector" do
# Flatpickr opens on current year by default, use current year to avoid changing year on tests.
let!(:current_year) { Time.now.year }
# Flatpickr opens on current month by default, use current month to avoid changing month on tests.
let!(:current_month) { Time.now.month }
let!(:user_15) { create :user, first_name: "Birthday on 15", birthday: "#{current_year}-#{current_month}-15 00:00:00" }
let!(:user_17) { create :user, first_name: "Birthday on 17", birthday: "#{current_year}-#{current_month}-17 00:00:00" }
let!(:user_20) { create :user, first_name: "Birthday on 20", birthday: "#{current_year}-#{current_month}-20 00:00:00" }

subject(:text_input) { find 'input[type="text"][placeholder="Filter by birthday"][data-date-time-filter-target="input"]' }

it "filters users by birthday range" do
visit avo.resources_users_path

expect(page).to have_text user_15.first_name
expect(page).to have_text user_17.first_name
expect(page).to have_text user_20.first_name

open_filters_menu

expect(page).to have_text "Apply birthday filter"

open_picker
set_picker_day "#{Date::MONTHNAMES[current_month]} 15, #{current_year}"
set_picker_day "#{Date::MONTHNAMES[current_month]} 17, #{current_year}"

click_on "Apply birthday filter"

expect(page).to have_text user_15.first_name
expect(page).to have_text user_17.first_name
expect(page).not_to have_text user_20.first_name
end
end

# TODO
# context "date with single selector" do
# end

# TODO
# context "date time with range selector" do
# end

# TODO
# context "date time with single selector" do
# end

context "time with single selector" do
let!(:course_16_10) { create :course, name: "starting at 16:10", starting_at: Time.parse("16:10:00") }
let!(:course_16_15) { create :course, name: "starting at 16:15", starting_at: Time.parse("16:15:18") }
let!(:course_16_15_19) { create :course, name: "starting at 16:15:19", starting_at: Time.parse("16:15:19") }
let!(:course_16_16) { create :course, name: "starting at 16:16", starting_at: Time.parse("16:16:00") }

subject(:text_input) { find 'input[type="text"][placeholder="Search by start time"][data-date-time-filter-target="input"]' }

it "filters courses by starting at" do
visit avo.resources_courses_path

expect(page).to have_text course_16_10.name
expect(page).to have_text course_16_15.name
expect(page).to have_text course_16_15_19.name
expect(page).to have_text course_16_16.name

open_filters_menu

expect(page).to have_text "Filter by start time"

open_picker
set_picker_hour 16
set_picker_minute 15
set_picker_second 18

all("button", text: "Filter by start time").first.trigger("click")

expect(page).not_to have_text course_16_10.name
expect(page).to have_text course_16_15.name
expect(page).not_to have_text course_16_15_19.name
expect(page).not_to have_text course_16_16.name
end
end
end
end

0 comments on commit b40b6b7

Please sign in to comment.