From 979bf49e245189aa60f53d0c2ebddb9b60fc6a10 Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 14:44:29 -0800 Subject: [PATCH 1/6] support by day --- src/Dictionaries/MonthDayRecurrence.php | 56 +++++++++++++++++++++++++ src/Types/RecurringEvent.php | 10 +++++ 2 files changed, 66 insertions(+) create mode 100644 src/Dictionaries/MonthDayRecurrence.php diff --git a/src/Dictionaries/MonthDayRecurrence.php b/src/Dictionaries/MonthDayRecurrence.php new file mode 100644 index 0000000..d0f13c1 --- /dev/null +++ b/src/Dictionaries/MonthDayRecurrence.php @@ -0,0 +1,56 @@ + 'First Sunday', 'value' => 'first_sunday', 'rrule' => '1SU'], + ['label' => 'First Monday', 'value' => 'first_monday', 'rrule' => '1MO'], + ['label' => 'First Tuesday', 'value' => 'first_tuesday', 'rrule' => '1TU'], + ['label' => 'First Wednesday', 'value' => 'first_wednesday', 'rrule' => '1WE'], + ['label' => 'First Thursday', 'value' => 'first_thursday', 'rrule' => '1TH'], + ['label' => 'First Friday', 'value' => 'first_friday', 'rrule' => '1FR'], + ['label' => 'First Saturday', 'value' => 'first_saturday', 'rrule' => '1SA'], + ['label' => 'Second Sunday', 'value' => 'second_sunday', 'rrule' => '2SU'], + ['label' => 'Second Monday', 'value' => 'second_monday', 'rrule' => '2MO'], + ['label' => 'Second Tuesday', 'value' => 'second_tuesday', 'rrule' => '2TU'], + ['label' => 'Second Wednesday', 'value' => 'second_wednesday', 'rrule' => '2WE'], + ['label' => 'Second Thursday', 'value' => 'second_thursday', 'rrule' => '2TH'], + ['label' => 'Second Friday', 'value' => 'second_friday', 'rrule' => '2FR'], + ['label' => 'Second Saturday', 'value' => 'second_saturday', 'rrule' => '2SA'], + ['label' => 'Third Sunday', 'value' => 'third_sunday', 'rrule' => '3SU'], + ['label' => 'Third Monday', 'value' => 'third_monday', 'rrule' => '3MO'], + ['label' => 'Third Tuesday', 'value' => 'third_tuesday', 'rrule' => '3TU'], + ['label' => 'Third Wednesday', 'value' => 'third_wednesday', 'rrule' => '3WE'], + ['label' => 'Third Thursday', 'value' => 'third_thursday', 'rrule' => '3TH'], + ['label' => 'Third Friday', 'value' => 'third_friday', 'rrule' => '3FR'], + ['label' => 'Third Saturday', 'value' => 'third_saturday', 'rrule' => '3SA'], + ['label' => 'Fourth Sunday', 'value' => 'fourth_sunday', 'rrule' => '4SU'], + ['label' => 'Fourth Monday', 'value' => 'fourth_monday', 'rrule' => '4MO'], + ['label' => 'Fourth Tuesday', 'value' => 'fourth_tuesday', 'rrule' => '4TU'], + ['label' => 'Fourth Wednesday', 'value' => 'fourth_wednesday', 'rrule' => '4WE'], + ['label' => 'Fourth Thursday', 'value' => 'fourth_thursday', 'rrule' => '4TH'], + ['label' => 'Fourth Friday', 'value' => 'fourth_friday', 'rrule' => '4FR'], + ['label' => 'Fourth Saturday', 'value' => 'fourth_saturday', 'rrule' => '4SA'], + ['label' => 'Fifth Sunday', 'value' => 'fifth_sunday', 'rrule' => '5SU'], + ['label' => 'Fifth Monday', 'value' => 'fifth_monday', 'rrule' => '5MO'], + ['label' => 'Fifth Tuesday', 'value' => 'fifth_tuesday', 'rrule' => '5TU'], + ['label' => 'Fifth Wednesday', 'value' => 'fifth_wednesday', 'rrule' => '5WE'], + ['label' => 'Fifth Thursday', 'value' => 'fifth_thursday', 'rrule' => '5TH'], + ['label' => 'Fifth Friday', 'value' => 'fifth_friday', 'rrule' => '5FR'], + ['label' => 'Fifth Saturday', 'value' => 'fifth_saturday', 'rrule' => '5SA'], + ['label' => 'Last Sunday', 'value' => 'last_sunday', 'rrule' => '-1 SU'], + ['label' => 'Last Monday', 'value' => 'last_monday', 'rrule' => '-1 MO'], + ['label' => 'Last Tuesday', 'value' => 'last_tuesday', 'rrule' => '-1 TU'], + ['label' => 'Last Wednesday', 'value' => 'last_wednesday', 'rrule' => '-1 WE'], + ['label' => 'Last Thursday', 'value' => 'last_thursday', 'rrule' => '-1 TH'], + ['label' => 'Last Friday', 'value' => 'last_friday', 'rrule' => '-1 FR'], + ['label' => 'Last Saturday', 'value' => 'last_saturday', 'rrule' => '-1 SA'], + ]; + } +} diff --git a/src/Types/RecurringEvent.php b/src/Types/RecurringEvent.php index 5a19573..6af7fe8 100644 --- a/src/Types/RecurringEvent.php +++ b/src/Types/RecurringEvent.php @@ -2,6 +2,7 @@ namespace TransformStudios\Events\Types; +use Illuminate\Support\Arr; use Illuminate\Support\Carbon; use RRule\RRule; use RRule\RRuleInterface; @@ -11,6 +12,11 @@ class RecurringEvent extends Event { + public function onSpecificDays(): array + { + return $this->specific_days ?? []; + } + public function interval(): int { return $this->interval ?? 1; @@ -44,6 +50,10 @@ protected function rule(): RRuleInterface $rule['until'] = Carbon::parse($end)->shiftTimezone($this->timezone['timezone'])->endOfDay(); } + if (! empty($days = $this->onSpecificDays())) { + $rule['byday'] = Arr::pluck($days, 'rrule'); + } + return new RRule($rule); } From 337eff87d56b151907b61186a20ad8093b5e980c Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 14:45:01 -0800 Subject: [PATCH 2/6] add fields for by day --- resources/fieldsets/event.yaml | 105 +++++++++------------------------ 1 file changed, 27 insertions(+), 78 deletions(-) diff --git a/resources/fieldsets/event.yaml b/resources/fieldsets/event.yaml index aa66414..3cd3c00 100644 --- a/resources/fieldsets/event.yaml +++ b/resources/fieldsets/event.yaml @@ -14,17 +14,6 @@ fields: width: 33 display: Recurrence default: none - instructions_position: above - listable: hidden - visibility: visible - replicator_preview: true - taggable: false - push_tags: false - multiple: false - clearable: false - searchable: true - cast_booleans: false - hide_display: false - handle: timezone field: @@ -36,9 +25,6 @@ fields: cast_booleans: false display: Timezone type: timezones - icon: select - listable: hidden - instructions_position: above mode: typeahead width: 33 - @@ -47,14 +33,25 @@ fields: type: toggle width: 33 display: 'All Day?' - instructions_position: above - listable: hidden - visibility: visible - replicator_preview: true - default: false - hide_display: false unless: recurrence: 'equals multi_day' + - + handle: by_day + field: + type: toggle + display: 'By Day' + width: 25 + if: + recurrence: 'equals monthly' + - + handle: specific_days + field: + dictionary: month_day_recurrence + type: dictionary + display: 'Which Day(s)' + width: 75 + if: + by_day: 'equals true' - handle: interval field: @@ -85,18 +82,8 @@ fields: input_format: M/D/YYYY width: 50 display: 'Start Date' - instructions_position: above - listable: hidden - visibility: visible - replicator_preview: true - mode: single inline: true full_width: true - columns: 1 - rows: 1 - time_enabled: false - time_seconds_enabled: false - hide_display: false unless_any: multi_day: 'equals true' recurrence: 'equals multi_day' @@ -110,19 +97,9 @@ fields: input_format: M/D/YYYY display: 'End Date' width: 50 - listable: hidden - mode: single - time_enabled: false time_required: false full_width: true inline: true - columns: 1 - rows: 1 - instructions_position: above - visibility: visible - replicator_preview: true - time_seconds_enabled: false - hide_display: false if: recurrence: 'contains_any daily, weekly, monthly, every' - @@ -132,12 +109,6 @@ fields: width: 25 display: 'Start Time' instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)' - instructions_position: above - listable: hidden - visibility: visible - replicator_preview: true - seconds_enabled: false - hide_display: false unless_any: multi_day: 'equals true' all_day: 'equals true' @@ -149,12 +120,6 @@ fields: width: 25 display: 'End Time' instructions: 'Input in [24-hour format](https://en.wikipedia.org/wiki/24-hour_clock)' - instructions_position: above - listable: hidden - visibility: visible - replicator_preview: true - seconds_enabled: false - hide_display: false unless_any: multi_day: 'equals true' all_day: 'equals true' @@ -163,7 +128,12 @@ fields: handle: days field: type: grid - mode: table + display: 'Event Days' + add_row: 'Add Day' + min_rows: 1 + if_any: + multi_day: 'equals true' + recurrence: 'equals multi_day' fields: - handle: date @@ -210,24 +180,14 @@ fields: field: 'events::event.all_day' config: width: 25 - display: 'Event Days' - add_row: 'Add Day' - listable: hidden - reorderable: true - instructions_position: above - visibility: visible - replicator_preview: true - min_rows: 1 - fullscreen: true - hide_display: false - if_any: - multi_day: 'equals true' - recurrence: 'equals multi_day' - handle: exclude_dates field: type: grid - mode: table + display: 'Exclude Days' + add_row: 'Add Day' + if_any: + recurrence: 'contains_any monthly, daily, weekly, every' fields: - handle: date @@ -238,14 +198,3 @@ fields: require_time: false input_format: M/D/YYYY display: Date - display: 'Exclude Days' - add_row: 'Add Day' - listable: hidden - reorderable: true - instructions_position: above - visibility: visible - replicator_preview: true - fullscreen: true - hide_display: false - if_any: - recurrence: 'contains_any monthly, daily, weekly, every' From 16ae1008397e0efab1130c1aa558e6439b58f61c Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 14:53:45 -0800 Subject: [PATCH 3/6] better test names --- tests/Unit/RecurringEventsTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/RecurringEventsTest.php b/tests/Unit/RecurringEventsTest.php index c5a80c7..46b728d 100755 --- a/tests/Unit/RecurringEventsTest.php +++ b/tests/Unit/RecurringEventsTest.php @@ -14,7 +14,7 @@ class RecurringEventsTest extends TestCase { #[Test] - public function canCreateRecurringEvent() + public function can_create_recurring_event() { $recurringEntry = Entry::make() ->collection('events') @@ -32,7 +32,7 @@ public function canCreateRecurringEvent() } #[Test] - public function wontCreateRecurringEventWhenMultiDay() + public function wont_create_recurring_event_when_multi_day() { $recurringEntry = Entry::make() ->collection('events') @@ -50,7 +50,7 @@ public function wontCreateRecurringEventWhenMultiDay() } #[Test] - public function canShowLastOccurrenceWhenNoEndTime() + public function can_show_last_occurrence_when_no_end_time() { Carbon::setTestNow(now()->setTimeFromTimeString('10:00')); From 73a4f42d5e33d4c374704032b049f0ab20c55fb6 Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 14:59:15 -0800 Subject: [PATCH 4/6] write and pass test --- src/Dictionaries/MonthDayRecurrence.php | 14 +++++++------- tests/Unit/RecurringEventsTest.php | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Dictionaries/MonthDayRecurrence.php b/src/Dictionaries/MonthDayRecurrence.php index d0f13c1..20479ec 100644 --- a/src/Dictionaries/MonthDayRecurrence.php +++ b/src/Dictionaries/MonthDayRecurrence.php @@ -44,13 +44,13 @@ protected function getItems(): array ['label' => 'Fifth Thursday', 'value' => 'fifth_thursday', 'rrule' => '5TH'], ['label' => 'Fifth Friday', 'value' => 'fifth_friday', 'rrule' => '5FR'], ['label' => 'Fifth Saturday', 'value' => 'fifth_saturday', 'rrule' => '5SA'], - ['label' => 'Last Sunday', 'value' => 'last_sunday', 'rrule' => '-1 SU'], - ['label' => 'Last Monday', 'value' => 'last_monday', 'rrule' => '-1 MO'], - ['label' => 'Last Tuesday', 'value' => 'last_tuesday', 'rrule' => '-1 TU'], - ['label' => 'Last Wednesday', 'value' => 'last_wednesday', 'rrule' => '-1 WE'], - ['label' => 'Last Thursday', 'value' => 'last_thursday', 'rrule' => '-1 TH'], - ['label' => 'Last Friday', 'value' => 'last_friday', 'rrule' => '-1 FR'], - ['label' => 'Last Saturday', 'value' => 'last_saturday', 'rrule' => '-1 SA'], + ['label' => 'Last Sunday', 'value' => 'last_sunday', 'rrule' => '-1SU'], + ['label' => 'Last Monday', 'value' => 'last_monday', 'rrule' => '-1MO'], + ['label' => 'Last Tuesday', 'value' => 'last_tuesday', 'rrule' => '-1TU'], + ['label' => 'Last Wednesday', 'value' => 'last_wednesday', 'rrule' => '-1WE'], + ['label' => 'Last Thursday', 'value' => 'last_thursday', 'rrule' => '-1TH'], + ['label' => 'Last Friday', 'value' => 'last_friday', 'rrule' => '-1FR'], + ['label' => 'Last Saturday', 'value' => 'last_saturday', 'rrule' => '-1SA'], ]; } } diff --git a/tests/Unit/RecurringEventsTest.php b/tests/Unit/RecurringEventsTest.php index 46b728d..74b1887 100755 --- a/tests/Unit/RecurringEventsTest.php +++ b/tests/Unit/RecurringEventsTest.php @@ -69,4 +69,26 @@ public function can_show_last_occurrence_when_no_end_time() $this->assertCount(2, $occurrences); } + + #[Test] + public function can_generate_monthly_by_day_occurrences() + { + Carbon::setTestNow(Carbon::parse('Jan 29 2025 10:00am')); + + $recurringEntry = tap(Entry::make() + ->collection('events') + ->data([ + 'start_date' => ray()->pass(Carbon::now()->addDays(1)->toDateString()), + 'start_time' => '22:00', + 'recurrence' => 'monthly', + 'end_date' => ray()->pass(Carbon::now()->addMonths(3)->toDateString()), + 'timezone' => 'America/Chicago', + 'specific_days' => ['first_sunday', 'last_wednesday'], + ]))->save(); + + $occurrences = Events::fromCollection(handle: 'events') + ->between(Carbon::now(), Carbon::now()->addMonths(4)); + + $this->assertCount(5, $occurrences); + } } From 00d3972461898cfeba9b3d54e07020bce530d4c3 Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 15:31:24 -0800 Subject: [PATCH 5/6] tidy fieldset --- resources/fieldsets/event.yaml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/resources/fieldsets/event.yaml b/resources/fieldsets/event.yaml index 3cd3c00..4057b7b 100644 --- a/resources/fieldsets/event.yaml +++ b/resources/fieldsets/event.yaml @@ -35,23 +35,15 @@ fields: display: 'All Day?' unless: recurrence: 'equals multi_day' - - - handle: by_day - field: - type: toggle - display: 'By Day' - width: 25 - if: - recurrence: 'equals monthly' - handle: specific_days field: dictionary: month_day_recurrence type: dictionary display: 'Which Day(s)' - width: 75 + width: 100 if: - by_day: 'equals true' + recurrence: 'equals monthly' - handle: interval field: From f89f9b0bed7f3e0453316225bf270fbd3d0e0d5e Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 29 Jan 2025 15:33:59 -0800 Subject: [PATCH 6/6] docs --- DOCUMENTATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 0b28540..f0d611f 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -31,6 +31,7 @@ You can also use our sample fieldset by importing `events::event`. * all the single day fields * `recurrence` - **Optional** - One of `daily`, `weekly`, `monthly`, `annually`, `every` +* `specific_days` - **Optional** - when `recurrence` is `monthly`, you can choose options like every 3rd Tuesday, etc. You can have more than one. * `interval` - **Optional** - required if `recurrence` is `every` and indicates the frequency of the event * `period` - **Optional** - required if `recurrence` is `every` and indicates the period of recurrence. One of `days`, `weeks`, `months`, `years` * `end_date` - **Optional** - when is the last event. If `recurrence` is set and this is not, the event goes on forever