Skip to content

Instantly share code, notes, and snippets.

@nowendwell
Last active March 31, 2025 17:57
Show Gist options
  • Save nowendwell/42f68ee513728732c0fca7c58764c3a5 to your computer and use it in GitHub Desktop.
Save nowendwell/42f68ee513728732c0fca7c58764c3a5 to your computer and use it in GitHub Desktop.
Laravel Livewire FullCalendar
<div>
@push('styles')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/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/[email protected]/main.min.js" integrity="sha256-rPPF6R+AH/Gilj2aC00ZAuB2EKmnEjXlEWx5MkAp7bw=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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' => [],
]);
}
}
@myseocompany
Copy link

Greath Code!
listen, you are using a Model named Event.
¿Where can I find it?

@modernben
Copy link

It's just a generic model with a title and some dates. You can make this whatever you are wanting to show in your calendar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment