From a49ff73b8f69f742e6f7d5c0295156ffb741cd42 Mon Sep 17 00:00:00 2001 From: Hasarinda Thenuwara Date: Wed, 28 Feb 2024 17:02:23 +1030 Subject: [PATCH] Add relative date formatting for date/datetime columns --- app/components/koi/tables/body.rb | 80 ++++++++++++++++--- app/helpers/koi/date_helper.rb | 44 ++++------ spec/components/koi/tables/body_spec.rb | 50 +++++++++++- .../koi/tables/table_component_spec.rb | 2 +- .../views/admin/banners/_banner.html+row.erb | 1 + 5 files changed, 137 insertions(+), 40 deletions(-) diff --git a/app/components/koi/tables/body.rb b/app/components/koi/tables/body.rb index 5535f26d9..8fd1006bb 100644 --- a/app/components/koi/tables/body.rb +++ b/app/components/koi/tables/body.rb @@ -11,32 +11,92 @@ def rendered_value end # Formats the value as a date - # - # default format is :admin + # @param format [String] date format, defaults to :admin + # @param relative [Boolean] if true, the date may be(if within 5 days) shown as a relative date class DateComponent < BodyCellComponent - def initialize(table, record, attribute, format: :admin, **options) + include Koi::DateHelper + + def initialize(table, record, attribute, format: :admin, relative: true, **options) super(table, record, attribute, **options) - @format = format + @format = format + @relative = relative + end + + def value + super&.to_date end def rendered_value - value.present? ? l(value.to_date, format: @format) : "" + @relative ? relative_time : absolute_time + end + + private + + def absolute_time + value.present? ? I18n.l(value, format: @format) : "" + end + + def relative_time + if value.blank? + "" + else + distance_from_now&.capitalize || absolute_time + end + end + + def default_html_attributes + @relative && distance_from_now.present? ? { title: absolute_time } : {} end end # Formats the value as a datetime - # - # default format is :admin + # @param format [String] datetime format, defaults to :admin + # @param relative [Boolean] if true, the datetime may be(if today) shown as a relative date/time class DatetimeComponent < BodyCellComponent - def initialize(table, record, attribute, format: :admin, **options) + include ActionView::Helpers::DateHelper + + def initialize(table, record, attribute, format: :admin, relative: true, **options) super(table, record, attribute, **options) - @format = format + @format = format + @relative = relative + end + + def value + super&.to_datetime end def rendered_value - value.present? ? l(value.to_datetime, format: @format) : "" + @relative ? relative_time : absolute_time + end + + private + + def absolute_time + value.present? ? I18n.l(value, format: @format) : "" + end + + def today? + value.to_date == Date.current + end + + def relative_time + return "" if value.blank? + + if today? + if value > DateTime.current + "#{distance_of_time_in_words(value, DateTime.current)} from now".capitalize + else + "#{distance_of_time_in_words(value, DateTime.current)} ago".capitalize + end + else + absolute_time + end + end + + def default_html_attributes + @relative && today? ? { title: absolute_time } : {} end end diff --git a/app/helpers/koi/date_helper.rb b/app/helpers/koi/date_helper.rb index 02c7f2c60..8c4550525 100644 --- a/app/helpers/koi/date_helper.rb +++ b/app/helpers/koi/date_helper.rb @@ -1,36 +1,24 @@ # frozen_string_literal: true -# rubocop:disable Naming/MethodName module Koi module DateHelper - # @deprecated - def date_format(date, format) - date.strftime format.gsub(/yyyy/, "%Y") - .gsub(/yy/, "%y") - .gsub(/Month/, "%B") - .gsub(/M/, "%b") - .gsub(/mm/, "%m") - .gsub(/m/, "%-m") - .gsub(/Day/, "%A") - .gsub(/D/, "%a") - .gsub(/dd/, "%d") - .gsub(/d/, "%-d") - end - - # @deprecated - def date_Month_d_yyyy(date) - date.strftime "%B %-d, %Y" - end - - # @deprecated - def date_d_Month_yyyy(date) - date.strftime "%-d %B %Y" - end + def distance_from_now + from_time = value.to_time + to_time = Date.current.to_time + distance_in_days = ((to_time - from_time) / (24.0 * 60.0 * 60.0)).round - # @deprecated - def date_d_M_yy(date) - date.strftime "%-d %b %y" + case distance_in_days + when 0 + "today" + when 1 + "yesterday" + when -1 + "tomorrow" + when 2..5 + "#{distance_in_days} days ago" + when -5..-2 + "#{distance_in_days.abs} days from now" + end end end end -# rubocop:enable Naming/MethodName diff --git a/spec/components/koi/tables/body_spec.rb b/spec/components/koi/tables/body_spec.rb index ff6db3911..fd1d1716f 100644 --- a/spec/components/koi/tables/body_spec.rb +++ b/spec/components/koi/tables/body_spec.rb @@ -95,6 +95,8 @@ end describe Koi::Tables::Body::DateComponent do + let(:record) { create(:post, published_on: 1.month.ago) } + it "renders column" do component = described_class.new(table, record, :published_on) rendered = render_inline(component) @@ -102,6 +104,42 @@ #{I18n.l(record.published_on, format: :admin)} HTML end + + context "when not relative" do + let(:record) { create(:post, published_on: Date.current) } + + it "renders column" do + component = described_class.new(table, record, :published_on, relative: false) + rendered = render_inline(component) + expect(rendered).to match_html(<<~HTML) + #{I18n.l(record.published_on, format: :admin)} + HTML + end + end + + context "when date is within 5 days" do + let(:record) { create(:post, published_on: 2.days.ago) } + + it "renders column" do + component = described_class.new(table, record, :published_on) + rendered = render_inline(component) + expect(rendered).to match_html(<<~HTML) + 2 days ago + HTML + end + end + + context "when future date" do + let(:record) { create(:post, published_on: 3.days.from_now) } + + it "renders column" do + component = described_class.new(table, record, :published_on) + rendered = render_inline(component) + expect(rendered).to match_html(<<~HTML) + 3 days from now + HTML + end + end end describe Koi::Tables::Body::DatetimeComponent do @@ -109,9 +147,19 @@ component = described_class.new(table, record, :created_at) rendered = render_inline(component) expect(rendered).to match_html(<<~HTML) - #{I18n.l(record.created_at, format: :admin)} + Less than a minute ago HTML end + + context "when not relative" do + it "renders column" do + component = described_class.new(table, record, :created_at, relative: false) + rendered = render_inline(component) + expect(rendered).to match_html(<<~HTML) + #{I18n.l(record.created_at, format: :admin)} + HTML + end + end end describe Koi::Tables::Body::NumberComponent do diff --git a/spec/components/koi/tables/table_component_spec.rb b/spec/components/koi/tables/table_component_spec.rb index 5af82587e..15dc8185a 100644 --- a/spec/components/koi/tables/table_component_spec.rb +++ b/spec/components/koi/tables/table_component_spec.rb @@ -85,7 +85,7 @@ expect(table).to match_html(<<~HTML) - +
Created at
#{value}
Less than a minute ago
HTML end diff --git a/spec/templates/app/views/admin/banners/_banner.html+row.erb b/spec/templates/app/views/admin/banners/_banner.html+row.erb index d6e0755e6..7efbdbb63 100644 --- a/spec/templates/app/views/admin/banners/_banner.html+row.erb +++ b/spec/templates/app/views/admin/banners/_banner.html+row.erb @@ -1,3 +1,4 @@ <% row.ordinal %> <% row.link :name %> <% row.image :image %> +<% row.datetime :created_at %>