From a93598ef2ec412a693c1e9b9da3b4540f9c85712 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode Date: Tue, 7 Feb 2023 15:18:47 +0100 Subject: [PATCH] recurring meetup events --- app/Http/Controllers/Api/MeetupController.php | 96 +++++++++++++ .../Livewire/Meetup/Form/MeetupEventForm.php | 128 ++++++++++++++++++ app/Http/Livewire/Tables/MeetupEventTable.php | 23 +++- app/Observers/MeetupEventObserver.php | 35 +++-- app/Support/Carbon.php | 11 ++ resources/lang/de.json | 9 +- resources/lang/en.json | 9 +- resources/lang/es.json | 9 +- resources/lang/fr.json | 9 +- resources/lang/hr.json | 9 +- resources/lang/it.json | 9 +- resources/lang/mk.json | 9 +- resources/lang/pl.json | 9 +- resources/lang/pt.json | 9 +- resources/lang/tr.json | 9 +- .../areas/toolbar-left-end.blade.php | 6 + .../columns/meetup_events/manage.blade.php | 16 +++ .../views/columns/meetups/name.blade.php | 1 - .../views/components/input/group.blade.php | 13 ++ .../meetup/form/meetup-event-form.blade.php | 105 ++++++++++++++ resources/views/navigation-menu.blade.php | 2 +- routes/api.php | 8 +- routes/web.php | 4 + 23 files changed, 502 insertions(+), 36 deletions(-) create mode 100644 app/Http/Controllers/Api/MeetupController.php create mode 100644 app/Http/Livewire/Meetup/Form/MeetupEventForm.php create mode 100644 resources/views/columns/meetup_events/areas/toolbar-left-end.blade.php create mode 100644 resources/views/columns/meetup_events/manage.blade.php create mode 100644 resources/views/components/input/group.blade.php create mode 100644 resources/views/livewire/meetup/form/meetup-event-form.blade.php diff --git a/app/Http/Controllers/Api/MeetupController.php b/app/Http/Controllers/Api/MeetupController.php new file mode 100644 index 00000000..85d9a28a --- /dev/null +++ b/app/Http/Controllers/Api/MeetupController.php @@ -0,0 +1,96 @@ +find($request->input('user_id'))->meetups->pluck('id'); + + return Meetup::query() + ->select('id', 'name', 'city_id') + ->with([ + 'city', + ]) + ->whereIn('id', $myMeetupIds->toArray()) + ->orderBy('name') + ->when( + $request->search, + fn(Builder $query) => $query + ->where('name', 'like', "%{$request->search}%") + ->orWhereHas('city', + fn(Builder $query) => $query->where('cities.name', 'ilike', "%{$request->search}%")) + ) + ->when( + $request->exists('selected'), + fn(Builder $query) => $query->whereIn('id', $request->input('selected', [])), + fn(Builder $query) => $query->limit(10) + ) + ->get() + ->map(function (Meetup $meetup) { + $meetup->profile_image = $meetup->getFirstMediaUrl('logo', 'thumb'); + + return $meetup; + }); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + // + } + + /** + * Display the specified resource. + * + * @param \App\Models\meetup $meetup + * + * @return \Illuminate\Http\Response + */ + public function show(meetup $meetup) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\Models\meetup $meetup + * + * @return \Illuminate\Http\Response + */ + public function update(Request $request, meetup $meetup) + { + // + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\meetup $meetup + * + * @return \Illuminate\Http\Response + */ + public function destroy(meetup $meetup) + { + // + } +} diff --git a/app/Http/Livewire/Meetup/Form/MeetupEventForm.php b/app/Http/Livewire/Meetup/Form/MeetupEventForm.php new file mode 100644 index 00000000..b9ad2452 --- /dev/null +++ b/app/Http/Livewire/Meetup/Form/MeetupEventForm.php @@ -0,0 +1,128 @@ + 'required', + 'meetupEvent.start' => 'required', + 'meetupEvent.location' => 'string|nullable', + 'meetupEvent.description' => 'string|nullable', + 'meetupEvent.link' => 'string|url|nullable', + + 'series.*.start' => 'required', + + 'recurring' => 'bool', + 'repetitions' => 'numeric|min:1', + ]; + } + + public function mount() + { + if (!$this->meetupEvent) { + $this->meetupEvent = new MeetupEvent( + [ + 'start' => now() + ->startOfDay() + ->addHours(17), + ] + ); + } + } + + public function updatedMeetupEventStart($value) + { + if ($this->recurring) { + $this->updatedRecurring(true); + } + } + + public function updatedRecurring($value) + { + if ($value && $this->meetupEvent->start) { + $this->series = []; + for ($i = 0; $i < $this->repetitions; $i++) { + $this->series[] = [ + 'start' => $this->meetupEvent->start->addWeeks($i + 1) + ->toDateTimeString(), + ]; + } + } + } + + public function updatedRepetitions($value) + { + if ($this->recurring) { + $this->updatedRecurring(true); + } + } + + public function submit() + { + $this->validate(); + if (!$this->meetupEvent->id) { + $hasAppointmentsOnThisDate = MeetupEvent::query() + ->where('meetup_id', $this->meetupEvent->meetup_id) + ->where('start', '>', Carbon::parse($this->meetupEvent->start) + ->startOfDay()) + ->where('start', '<', Carbon::parse($this->meetupEvent->start) + ->endOfDay()) + ->exists(); + if ($hasAppointmentsOnThisDate) { + $this->notification() + ->warning(__('There is already an event on this date. Please choose another date or delete the existing events.')); + + return; + } + } + + $this->meetupEvent->save(); + + if (!$this->meetupEvent->id && $this->recurring) { + foreach ($this->series as $event) { + $hasAppointmentsOnThisDate = MeetupEvent::query() + ->where('meetup_id', $this->meetupEvent->meetup_id) + ->where('start', '>', Carbon::parse($event['start']) + ->startOfDay()) + ->where('start', '<', Carbon::parse($event['start']) + ->endOfDay()) + ->exists(); + + if ($hasAppointmentsOnThisDate) { + continue; + } + + $this->meetupEvent->replicate() + ->fill($event) + ->saveQuietly(); + } + } + + $this->notification() + ->success(__('Event saved successfully.')); + + return to_route('meetup.table.meetupEvent', ['country' => $this->country]); + } + + public function render() + { + return view('livewire.meetup.form.meetup-event-form'); + } +} diff --git a/app/Http/Livewire/Tables/MeetupEventTable.php b/app/Http/Livewire/Tables/MeetupEventTable.php index 9a8a706f..67da208e 100644 --- a/app/Http/Livewire/Tables/MeetupEventTable.php +++ b/app/Http/Livewire/Tables/MeetupEventTable.php @@ -32,7 +32,14 @@ class MeetupEventTable extends DataTableComponent ]; }) ->setColumnSelectStatus(false) - ->setPerPage(10); + ->setPerPage(10) + ->setConfigurableAreas([ + 'toolbar-left-end' => [ + 'columns.meetup_events.areas.toolbar-left-end', [ + 'country' => $this->country, + ], + ], + ]); } public function filters(): array @@ -53,7 +60,7 @@ class MeetupEventTable extends DataTableComponent public function columns(): array { - return [ + $columns = [ Column::make(__('Meetup'), 'meetup.name') ->format( fn($value, $row, Column $column) => view('columns.meetup_events.name') @@ -72,12 +79,22 @@ class MeetupEventTable extends DataTableComponent ) ->sortable() ->collapseOnMobile(), - Column::make(__('Link'), 'link') + Column::make(__('Link')) ->label( fn($row, Column $column) => view('columns.meetup_events.link') ->withRow($row) ), ]; + + $adminColumns = auth()->check() ? [ + Column::make(__('Actions')) + ->label( + fn($row, Column $column) => view('columns.meetup_events.manage') + ->withRow($row) + ), + ] : []; + + return array_merge($columns, $adminColumns); } public function builder(): Builder diff --git a/app/Observers/MeetupEventObserver.php b/app/Observers/MeetupEventObserver.php index 4d1f6d81..ad7b2067 100644 --- a/app/Observers/MeetupEventObserver.php +++ b/app/Observers/MeetupEventObserver.php @@ -4,6 +4,7 @@ namespace App\Observers; use App\Models\MeetupEvent; use App\Traits\TwitterTrait; +use Illuminate\Support\Facades\Log; class MeetupEventObserver { @@ -18,23 +19,27 @@ class MeetupEventObserver */ public function created(MeetupEvent $meetupEvent) { - if (config('feeds.services.twitterAccountId')) { - $this->setNewAccessToken(1); + try { + if (config('feeds.services.twitterAccountId')) { + $this->setNewAccessToken(1); - $meetupName = $meetupEvent->meetup->name; - if ($meetupEvent->meetup->twitter_username) { - $meetupName .= ' @'.$meetupEvent->meetup->twitter_username; + $meetupName = $meetupEvent->meetup->name; + if ($meetupEvent->meetup->twitter_username) { + $meetupName .= ' @'.$meetupEvent->meetup->twitter_username; + } + + $text = sprintf("%s hat einen neuen Termin eingestellt:\n\n%s\n\n%s\n\n%s\n\n#Bitcoin #Meetup #Einundzwanzig #gesundesgeld", + $meetupName, + $meetupEvent->start->asDateTime(), + $meetupEvent->location, + url()->route('meetup.event.landing', + ['country' => 'de', 'meetup' => $meetupEvent->id]), + ); + + $this->postTweet($text); } - - $text = sprintf("%s hat einen neuen Termin eingestellt:\n\n%s\n\n%s\n\n%s\n\n#Bitcoin #Meetup #Einundzwanzig #gesundesgeld", - $meetupName, - $meetupEvent->start->asDateTime(), - $meetupEvent->location, - url()->route('meetup.event.landing', - ['country' => 'de', 'meetup' => $meetupEvent->id]), - ); - - $this->postTweet($text); + } catch (\Exception $e) { + Log::error($e->getMessage()); } } diff --git a/app/Support/Carbon.php b/app/Support/Carbon.php index 76404fa8..6dba325a 100644 --- a/app/Support/Carbon.php +++ b/app/Support/Carbon.php @@ -18,6 +18,17 @@ class Carbon extends CarbonImmutable ->format('H:i'); } + public function asDayNameAndMonthName(): string + { + $dt = $this->timezone(config('app.user-timezone'))->locale('de'); + return sprintf("%s, %s. week of %s [%s]", + $dt->dayName, + $dt->weekNumberInMonth, + $dt->monthName, + $dt->timezoneAbbreviatedName + ); + } + public function asDateTime(): string { $dt = $this->timezone(config('app.user-timezone'))->locale('de'); diff --git a/resources/lang/de.json b/resources/lang/de.json index dd98f807..2a8e028f 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -711,5 +711,12 @@ "Unfortunately I cannot come": "Leider kann ich doch nicht kommen", "Link to participate": "Link zur Teilnahme", "Copied!": "Kopiert!", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "Zum Beispiel ein Link zu einer Location auf Google Maps oder ein Link zu einer Website. (nicht dein Telegram-Gruppen-Link)" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "Zum Beispiel ein Link zu einer Location auf Google Maps oder ein Link zu einer Website. (nicht dein Telegram-Gruppen-Link)", + "There is already an event on this date. Please choose another date or delete the existing events.": "Es gibt bereits einen Termin an diesem Datum. Bitte wähle ein anderes Datum oder lösche die bestehenden Termine.", + "Event saved successfully.": "Termin erfolgreich gespeichert.", + "no authorization": "keine Berechtigung", + "Recurring appointment": "Wiederkehrender Termin", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "Die Termine werden in der Datenbank als neue Einträge angelegt. Bitte sei vorsichtig mit dieser Funktion, sonst musst du alle Termine, ändern oder löschen, wenn du einen Fehler machst.", + "Number of repetitions": "Anzahl der Wiederholungen", + "Recurring appointments": "Wiederkehrende Termine" } diff --git a/resources/lang/en.json b/resources/lang/en.json index 6106c627..46d12c9f 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -706,5 +706,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/es.json b/resources/lang/es.json index 3d6c0a10..fe1b3be2 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -706,5 +706,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/fr.json b/resources/lang/fr.json index d77642f2..6449277e 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/hr.json b/resources/lang/hr.json index 8e985016..f8f86801 100644 --- a/resources/lang/hr.json +++ b/resources/lang/hr.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/it.json b/resources/lang/it.json index 6a901846..d2065d05 100644 --- a/resources/lang/it.json +++ b/resources/lang/it.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/mk.json b/resources/lang/mk.json index 8185dad7..502b247f 100644 --- a/resources/lang/mk.json +++ b/resources/lang/mk.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/pl.json b/resources/lang/pl.json index e8053110..33e6ab5b 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/pt.json b/resources/lang/pt.json index 7e23cbe3..5c4b07c9 100644 --- a/resources/lang/pt.json +++ b/resources/lang/pt.json @@ -707,5 +707,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/lang/tr.json b/resources/lang/tr.json index 77fc0697..97df687d 100644 --- a/resources/lang/tr.json +++ b/resources/lang/tr.json @@ -681,5 +681,12 @@ "Unfortunately I cannot come": "", "Link to participate": "", "Copied!": "", - "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "" + "For example, a link to a location on Google Maps or a link to a website. (not your Telegram group link)": "", + "There is already an event on this date. Please choose another date or delete the existing events.": "", + "Event saved successfully.": "", + "no authorization": "", + "Recurring appointment": "", + "The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.": "", + "Number of repetitions": "", + "Recurring appointments": "" } \ No newline at end of file diff --git a/resources/views/columns/meetup_events/areas/toolbar-left-end.blade.php b/resources/views/columns/meetup_events/areas/toolbar-left-end.blade.php new file mode 100644 index 00000000..3fbaa302 --- /dev/null +++ b/resources/views/columns/meetup_events/areas/toolbar-left-end.blade.php @@ -0,0 +1,6 @@ +
+ + + {{ __('Register Meetup date') }} + +
diff --git a/resources/views/columns/meetup_events/manage.blade.php b/resources/views/columns/meetup_events/manage.blade.php new file mode 100644 index 00000000..dd3a938e --- /dev/null +++ b/resources/views/columns/meetup_events/manage.blade.php @@ -0,0 +1,16 @@ +
+
+ @if(auth()->user()->can('update', $row)) + + + {{ __('Edit') }} + + @else + {{ __('no authorization') }} + @endif +
+
diff --git a/resources/views/columns/meetups/name.blade.php b/resources/views/columns/meetups/name.blade.php index 2ae67341..3afa22d1 100644 --- a/resources/views/columns/meetups/name.blade.php +++ b/resources/views/columns/meetups/name.blade.php @@ -5,5 +5,4 @@ {{ $row->name }} - diff --git a/resources/views/components/input/group.blade.php b/resources/views/components/input/group.blade.php new file mode 100644 index 00000000..fd9d40ee --- /dev/null +++ b/resources/views/components/input/group.blade.php @@ -0,0 +1,13 @@ +@props([ + 'for', + 'label', +]) +
+ +
+ {{ $slot }} +
+
diff --git a/resources/views/livewire/meetup/form/meetup-event-form.blade.php b/resources/views/livewire/meetup/form/meetup-event-form.blade.php new file mode 100644 index 00000000..34f37c45 --- /dev/null +++ b/resources/views/livewire/meetup/form/meetup-event-form.blade.php @@ -0,0 +1,105 @@ +
+ +
+

{{ __('Meetup Event') }}

+
+ {{ __('Back') }} +
+
+ +
+
+
+ + + + + + + + + + @if(!$meetupEvent->id) + + +

{{ __('The recurring appointments are created in the database as new entries. Please be careful with this function, otherwise you will have to change or delete all the appointments you have created manually if you make an error.') }}

+
+ @endif + + @if($recurring) + + + + @endif + + + + + + + + + + + + + + + @if($meetupEvent->start && $recurring) +
+ @for($i = 0; $i < $repetitions; $i++) + + @endfor +
+ @endif +
+ + + + + {{ __('Save') }} + + + +
+
+
+
diff --git a/resources/views/navigation-menu.blade.php b/resources/views/navigation-menu.blade.php index 9f7b3bb0..77d2ac17 100644 --- a/resources/views/navigation-menu.blade.php +++ b/resources/views/navigation-menu.blade.php @@ -30,7 +30,7 @@
- + {{ __('Register Meetup date') }} diff --git a/routes/api.php b/routes/api.php index 1d394773..70399eb4 100644 --- a/routes/api.php +++ b/routes/api.php @@ -24,14 +24,10 @@ Route::middleware('auth:sanctum') }); Route::middleware([]) - ->as('api.') + ->as('api.') ->group(function () { Route::resource('countries', \App\Http\Controllers\Api\CountryController::class); - }); - -Route::middleware([]) - ->as('api.') - ->group(function () { + Route::resource('meetup', \App\Http\Controllers\Api\MeetupController::class); Route::resource('languages', \App\Http\Controllers\Api\LanguageController::class); Route::get('meetups', function () { return \App\Models\Meetup::query() diff --git a/routes/web.php b/routes/web.php index 4366e070..abbded1c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -151,6 +151,10 @@ Route::middleware([]) Route::get('overview', \App\Http\Livewire\Meetup\MeetupTable::class) ->name('table.meetup'); + Route::get('/meetup-events/form/{meetupEvent?}', \App\Http\Livewire\Meetup\Form\MeetupEventForm::class) + ->name('event.form') + ->middleware('needMeetup'); + Route::get('/meetup-events/l/{meetupEvent}', \App\Http\Livewire\Meetup\LandingPageEvent::class) ->name('event.landing');