Skip to content

Instantly share code, notes, and snippets.

@oliuz
Forked from nowendwell/Calendar.php
Created November 6, 2023 18:27
Show Gist options
  • Save oliuz/1a933e4eb9a4b5d88b65921404f7460a to your computer and use it in GitHub Desktop.
Save oliuz/1a933e4eb9a4b5d88b65921404f7460a to your computer and use it in GitHub Desktop.
Laravel Livewire FullCalendar
<div>
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.5.1/main.min.css" integrity="sha256-uq9PNlMzB+1h01Ij9cx7zeE2OR2pLAfRw3uUUOOPKdA=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<style>
:root {
--fc-event-bg-color: var(--bs-primary) !important;
--fc-event-border-color: var(--bs-primary) !important;
}
.fc-event-title a {
color: white !important;
}
.fc-daygrid-event {
border-radius: 50em;
padding: 3px 10px;
}
</style>
@endpush
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-12 mb-5">
<div class="row">
<div class="col-lg-3">
<div id="external-events">
<p class="font-bold">Add to Calendar</p>
<button type="button" class="btn btn-primary text-start w-100 mb-2 px-3" wire:click="newEvent('event')" data-bs-toggle="modal" data-bs-target="#addCustomEvent">
<i class="fas fa-plus"></i> Event
</button>
<hr />
<button type="button" class="btn btn-primary text-start w-100 mb-2 px-3" wire:click="newEvent('custom')" data-bs-toggle="modal" data-bs-target="#addCustomEvent">
<i class="fas fa-plus"></i> Custom
</button>
<!-- Modal -->
<div class="modal fade" id="addCustomEvent" tabindex="-1" role="dialog" aria-labelledby="addCustomEventLabel" aria-hidden="true" wire:ignore.self>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<form wire:submit.prevent="save">
<div class="modal-header">
<h5 class="modal-title" id="addCustomEventLabel">Add Event to Calendar</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
@if($newType == "custom" || $newType == "event")
<div class="mb-3">
<label class="form-label" for="label">Title</label>
<input type="text" id="label" class="form-control" wire:model.defer="event.title" required />
</div>
@endif
@if($newType == "custom" || $newType == "event")
<div class="mb-3">
<label class="form-label" for="date">Start Date</label>
<input type="text" id="start_date" class="form-control flatpickr" wire:model.defer="event.start_date" placeholder="mm/dd/yyyy" required />
</div>
<div class="mb-3">
<label class="form-label" for="date">End Date</label>
<input type="text" id="end_date" class="form-control flatpickr" wire:model.defer="event.end_date" placeholder="mm/dd/yyyy" required />
</div>
@else
<div class="mb-3">
<label class="form-label" for="date">Schedule Date</label>
<input type="text" id="start_date" class="form-control flatpickr" wire:model.defer="event.start_date" placeholder="mm/dd/yyyy" required />
</div>
@endif
<div class="mb-3">
<label class="form-label" for="time">Time</label>
<input type="text" id="time" class="form-control" wire:model.defer="event.time" />
</div>
<div class="mb-3">
<label class="form-label" for="intensity">Intensity</label>
<input type="text" id="intensity" class="form-control" wire:model.defer="event.intensity" />
</div>
<div class="mb-3">
<label class="form-label" for="notes">Notes</label>
<textarea id="notes" class="form-control" wire:model.defer="event.notes"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary"">Add</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div id="calendar-container" wire:ignore>
<div id="calendar"></div>
</div>
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.9/flatpickr.min.js" integrity="sha512-+ruHlyki4CepPr07VklkX/KM5NXdD16K1xVwSva5VqOVbsotyCQVKEwdQ1tAeo3UkHCXfSMtKU/mZpKjYqkxZA==" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.5.1/main.min.js" integrity="sha256-rPPF6R+AH/Gilj2aC00ZAuB2EKmnEjXlEWx5MkAp7bw=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.5.1/locales-all.min.js" integrity="sha256-/ZgxvDj3QtyBZNLbfJaHdwbHF8R6OW82+5MT5yBsH9g=" crossorigin="anonymous"></script>
<script>
String.prototype.stripSlashes = function(){
return this.replace(/\\(.)/mg, "$1");
}
function nl2br (str, is_xhtml) {
if (typeof str === 'undefined' || str === null) {
return '';
}
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2');
}
$(document).on("click", ".popover .close" , function(){
$(this).parents(".popover").popover('hide');
});
document.addEventListener('reloadModalJS', function() {
$(".flatpickr").flatpickr({
dateFormat: 'm/d/Y'
});
});
document.addEventListener('livewire:load', function() {
$(document).ready(function(){
$(".flatpickr").flatpickr({
dateFormat: 'm/d/Y'
});
});
var Calendar = FullCalendar.Calendar;
var Draggable = FullCalendar.Draggable;
var containerEl = document.getElementById('external-events');
var calendarEl = document.getElementById('calendar');
var checkbox = document.getElementById('drop-remove');
// initialize the external events
// -----------------------------------------------------------------
new Draggable(containerEl, {
itemSelector: 'li',
eventData: function(eventEl) {
return {
title: eventEl.innerText
};
}
});
// initialize the calendar
// -----------------------------------------------------------------
var calendar = new Calendar(calendarEl, {
locale: '{{ config('app.locale') }}',
editable: true,
eventDurationEditable: false, // cant resize events to multiple days
droppable: true, // this allows things to be dropped onto the calendar
eventAdd: info => @this.eventReceive(info.event),
eventReceive: info => @this.eventReceive(info.event),
eventRemove: info => @this.eventRemove(info.event),
drop: function(info) {
// if (shouldRemove) {
// if so, remove the element from the "Draggable Events" list
// info.draggedEl.parentNode.removeChild(info.draggedEl);
// }
},
eventDrop: function(info){
if (confirm("Are you sure about this change?")) {
// reschedule event
@this.eventDrop(info.event, info.oldEvent)
} else {
info.revert();
}
},
eventClick: function(info) {
info.jsEvent.preventDefault(); // don't let the browser navigate
if (info.event.url) {
window.open(info.event.url);
}
$('.popover .close').trigger('click');
@this.setEvent(info.event);
$(info.jsEvent.target).popover({
html: true,
sanitize: false,
title: info.event.title + '<a href="#" class="close" data-dismiss="alert">&times;</a>',
content: nl2br(info.event.extendedProps.content.stripSlashes()),
// placement: 'top',
container: '#calendar'
}).popover('show');
return false;
},
loading: function(isLoading) {
if (!isLoading) {
this.getEvents().forEach(function(e){
if (e.source === null) {
e.remove();
}
});
}
},
events: function(fetchInfo, successCallback, failureCallback) {
@this.set('startDate', fetchInfo.start);
@this.set('endDate', fetchInfo.end);
@this.getEvents().then(results => {
successCallback(results);
});
},
});
calendar.render();
@this.on('refreshCalendar', function(){
calendar.refetchEvents()
});
@this.on('closeModal', function(){
$('.modal').modal('hide');
});
$("body").on("keyup", '#quick_notes', _.debounce(function(){
@this.saveNotes($(this).val());
calendar.refetchEvents();
}, 500));
$('#saveEvent').click(function(){
calendar.addEvent({
title: $('#label').val(),
allDay: true,
start: $('#start_date').val(),
end: $('#end_date') ? $('#end_date').val() : $('#start_date').val(),
extendedProps: {
content: $('#notes').val(),
meta: {
time: $('#time').val(),
intensity: $('#intensity').val(),
notes: $('#notes').val(),
}
}
});
});
$('body').on('click', '.delete_event', function(){
var c = confirm('Are you sure you want to delete this?');
if(c === true) {
var id = $(this).attr('data-id');
var event = calendar.getEventById(id);
event.remove(id);
$('.popover .close').trigger('click');
}
});
});
</script>
@endpush
</div>
<?php
namespace App\Http\Livewire;
use App\Models\Event;
use Livewire\Component;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
class DashboardCalendar extends Component
{
public $startDate;
public $endDate;
public $event;
public $activeEvent;
public $newType;
public function mount()
{
$this->startDate = now()->startOfMonth();
$this->endDate = now()->endOfMonth();
}
public function setEvent($event)
{
$our_event = Event::where('id', $event['id'])->firstOrFail();
$this->activeEvent = $our_event;
}
public function eventReceive($data)
{
// Handle new events
$event = Event::create([
'user_id' => auth()->id(),
'title' => $data['title'],
'content' => '',
'editable' => true,
'start' => $data['start'],
'created_by' => auth()->id(),
]);
if (isset($data['extendedProps']['meta'])) {
$event->meta = $data['extendedProps']['meta'];
$event->save();
}
$this->clearCache();
$this->emit('refreshCalendar');
}
public function eventDrop($event, $oldEvent)
{
// Modify existing events
$dh_event = Event::find($event['id']);
if ($dh_event->user_id != auth()->id() || $dh_event->created_by != auth()->id()) {
abort(403);
}
$dh_event->start = $event['start'];
$dh_event->save();
$this->clearCache();
$this->emit('refreshCalendar');
}
public function eventRemove($event)
{
$id = $event['id'];
$our_event = Event::find($id);
if ($our_event->created_by == auth()->id()) {
$our_event->delete();
} else {
abort(403);
}
$this->reset('activeEvent');
}
public function getEvents()
{
$startDate = $this->startDate;
$endDate = $this->endDate;
// $events = Cache::remember('user-events-' . auth()->id() . '-' . base64_encode($startDate.$endDate), now()->addHour(), function() use ($startDate, $endDate){
$events = [];
// add other events
$global_events = Event::query()
->where(function ($query) {
return $query->where('user_id', auth()->id())->orWhereNull('user_id');
})
->whereDate('start', '>=', $startDate)
->where(function ($query) use ($endDate) {
return $query->whereDate('end', '<=', $endDate)->orWhereNull('end');
})
->get();
foreach ($global_events as $global_event) {
$event = [
'id' => $global_event->id,
'title' => $global_event->title,
'start' => $global_event->start->startOfDay()->format('Y-m-d'),
'end' => $global_event->end ? $global_event->end->addDay()->startOfDay()->format('Y-m-d') : null,
'allDay' => $global_event->allDay,
'editable' => $global_event->editable,
'deletable' => $global_event->created_by == auth()->id(),
'backgroundColor' => $global_event->backgroundColor,
'extendedProps' => [
'type' => 'Event',
'content' => $global_event->content,
'notes' => $global_event->notes,
'editable' => $global_event->editable,
],
];
$events[] = $event;
}
// return $events;
// });
return $events;
}
public function clearCache()
{
Cache::forget('user-events-' . auth()->id() . '-' . base64_encode($this->startDate . $this->endDate));
}
public function newEvent($type)
{
$this->reset('event');
$this->newType = $type;
$this->dispatchBrowserEvent('reloadModalJS');
}
public function save()
{
/** @var App\Models\User */
$user = user();
$title = '';
if ($this->newType == 'rest') {
$title = 'Rest';
} elseif ($this->newType == 'ride') {
$title = $this->event['riding_style'] . ' Ride';
} else {
$title = $this->event['title'];
}
$title = Purifier::clean($title);
$user->events()->create([
'title' => $title,
'type' => $this->newType,
'content' => '',
'start' => Carbon::parse($this->event['start_date'])->format('Y-m-d'),
'end' => ! empty($this->event['end_date']) ? Carbon::parse($this->event['end_date'])->format('Y-m-d') : null,
'editable' => 1,
'allDay' => 1,
'meta' => [
'riding_style' => Purifier::clean(@$this->event['riding_style']),
'time' => Purifier::clean(@$this->event['time']),
'intensity' => Purifier::clean(@$this->event['intensity']),
'notes' => Purifier::clean(@$this->event['notes']),
],
'notes' => Purifier::clean(@$this->event['notes']),
'created_by' => auth()->id(),
]);
$this->reset('event');
$this->clearCache();
$this->emit('refreshCalendar');
$this->emitSelf('closeModal');
}
public function render()
{
return view('livewire.calendar', [
'events' => [],
]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment