Full Code of GuavaCZ/calendar for AI

main 6caa50e4ccad cached
111 files
183.1 KB
45.1k tokens
269 symbols
1 requests
Download .txt
Showing preview only (208K chars total). Download the full file or copy to clipboard to get everything.
Repository: GuavaCZ/calendar
Branch: main
Commit: 6caa50e4ccad
Files: 111
Total size: 183.1 KB

Directory structure:
gitextract_73e1rlqs/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── SECURITY.md
│   └── workflows/
│       ├── phpstan.yml
│       ├── release.yml
│       └── run-tests.yml
├── .gitignore
├── .gitmodules
├── .releaserc.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── bin/
│   └── build.js
├── composer.json
├── config/
│   └── calendar.php
├── database/
│   ├── factories/
│   │   └── ModelFactory.php
│   └── migrations/
│       └── create_calendar_table.php.stub
├── dist/
│   └── js/
│       ├── calendar-context-menu.js
│       ├── calendar-event.js
│       └── calendar.js
├── package.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── pint.json
├── resources/
│   ├── css/
│   │   └── theme.css
│   ├── js/
│   │   ├── calendar-context-menu.js
│   │   ├── calendar-event.js
│   │   └── calendar.js
│   ├── lang/
│   │   ├── cs/
│   │   │   └── translations.php
│   │   ├── de/
│   │   │   └── translations.php
│   │   ├── en/
│   │   │   └── translations.php
│   │   └── fr/
│   │       └── translations.php
│   └── views/
│       ├── .gitkeep
│       ├── components/
│       │   └── context-menu.blade.php
│       └── widgets/
│           └── calendar-widget.blade.php
├── src/
│   ├── Attributes/
│   │   ├── CalendarEventContent.php
│   │   ├── CalendarResourceLabelContent.php
│   │   └── CalendarSchema.php
│   ├── CalendarPlugin.php
│   ├── CalendarServiceProvider.php
│   ├── Concerns/
│   │   ├── CalendarAction.php
│   │   ├── CanRefreshCalendar.php
│   │   ├── CanUseFilamentTimezone.php
│   │   ├── HandlesDateClick.php
│   │   ├── HandlesDateSelect.php
│   │   ├── HandlesDatesSet.php
│   │   ├── HandlesEventAllUpdated.php
│   │   ├── HandlesEventClick.php
│   │   ├── HandlesEventDragAndDrop.php
│   │   ├── HandlesEventResize.php
│   │   ├── HandlesNoEventsClick.php
│   │   ├── HandlesViewDidMount.php
│   │   ├── HasAuthorization.php
│   │   ├── HasCalendarContextData.php
│   │   ├── HasCalendarView.php
│   │   ├── HasContextMenu.php
│   │   ├── HasDayMaxEvents.php
│   │   ├── HasDefaultActions.php
│   │   ├── HasEventContent.php
│   │   ├── HasEvents.php
│   │   ├── HasFirstDay.php
│   │   ├── HasFooterActions.php
│   │   ├── HasHeaderActions.php
│   │   ├── HasHeading.php
│   │   ├── HasLocale.php
│   │   ├── HasMoreLinkContent.php
│   │   ├── HasNotifications.php
│   │   ├── HasOptions.php
│   │   ├── HasResourceLabelContent.php
│   │   ├── HasResources.php
│   │   ├── HasSchema.php
│   │   ├── HasTheme.php
│   │   ├── InteractsWithCalendar.php
│   │   └── InteractsWithEventRecord.php
│   ├── Contracts/
│   │   ├── ContextualInfo.php
│   │   ├── Eventable.php
│   │   ├── HasCalendar.php
│   │   └── Resourceable.php
│   ├── Enums/
│   │   ├── CalendarViewType.php
│   │   └── Context.php
│   ├── Exceptions/
│   │   └── SchemaNotFoundException.php
│   ├── Filament/
│   │   ├── Actions/
│   │   │   ├── CreateAction.php
│   │   │   ├── DeleteAction.php
│   │   │   ├── EditAction.php
│   │   │   └── ViewAction.php
│   │   └── CalendarWidget.php
│   ├── ValueObjects/
│   │   ├── CalendarEvent.php
│   │   ├── CalendarResource.php
│   │   ├── CalendarView.php
│   │   ├── DateClickInfo.php
│   │   ├── DateSelectInfo.php
│   │   ├── DatesSetInfo.php
│   │   ├── EventAllUpdatedInfo.php
│   │   ├── EventClickInfo.php
│   │   ├── EventDropInfo.php
│   │   ├── EventResizeInfo.php
│   │   ├── FetchInfo.php
│   │   ├── NoEventsClickInfo.php
│   │   └── ViewDidMountInfo.php
│   └── helpers.php
├── tests/
│   ├── ArchTest.php
│   ├── Pest.php
│   ├── TestCase.php
│   └── Unit/
│       └── ValueObjects/
│           └── EventTest.php
└── workbench/
    └── app/
        └── Providers/
            └── WorkbenchServiceProvider.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2


================================================
FILE: .gitattributes
================================================
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.github            export-ignore
/.gitattributes     export-ignore
/.gitignore         export-ignore
/.gitmodules        export-ignore
/phpunit.xml.dist   export-ignore
/art                export-ignore
/docs               export-ignore
/demo               export-ignore
/tests              export-ignore
/workbench          export-ignore
/.editorconfig      export-ignore
/.php_cs.dist.php   export-ignore
/psalm.xml          export-ignore
/psalm.xml.dist     export-ignore
/testbench.yaml     export-ignore
/UPGRADING.md       export-ignore
/phpstan.neon.dist  export-ignore
/phpstan-baseline.neon  export-ignore


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Contributions are **welcome** and will be fully **credited**.

Please read and understand the contribution guide before creating an issue or pull request.

## Etiquette

This project is open source, and as such, the maintainers give their free time to build and maintain the source code
held within. They make the code freely available in the hope that it will be of use to other developers. It would be
extremely unfair for them to suffer abuse or anger for their hard work.

Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
world that developers are civilized and selfless people.

It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.

## Viability

When requesting or submitting new features, first consider whether it might be useful to others. Open
source projects are used by many developers, who may have entirely different needs to your own. Think about
whether or not your feature is likely to be used by other users of the project.

## Procedure

Before filing an issue:

- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
- Check to make sure your feature suggestion isn't already present within the project.
- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
- Check the pull requests tab to ensure that the feature isn't already in progress.

Before submitting a pull request:

- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

## Requirements

If the project maintainer has any additional requirements, you will find them listed here.

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.

**Happy coding**!


================================================
FILE: .github/FUNDING.yml
================================================
github: GuavaCZ


================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: Bug Report
description: Report an Issue or Bug with the Package
title: "[Bug]: "
labels: ["bug"]
body:
    - type: markdown
      attributes:
          value: |
              We're sorry to hear you have a problem. Can you help us solve it by providing the following details.
    - type: textarea
      id: what-happened
      attributes:
          label: What happened?
          description: What did you expect to happen?
          placeholder: I cannot currently do X thing because when I do, it breaks X thing.
      validations:
          required: true
    - type: textarea
      id: how-to-reproduce
      attributes:
          label: How to reproduce the bug
          description: How did this occur, please add any config values used and provide a set of reliable steps if possible.
          placeholder: When I do X I see Y.
      validations:
          required: true
    - type: input
      id: package-version
      attributes:
          label: Package Version
          description: What version of our Package are you running? Please be as specific as possible
          placeholder: 2.0.0
      validations:
          required: true
    - type: input
      id: php-version
      attributes:
          label: PHP Version
          description: What version of PHP are you running? Please be as specific as possible
          placeholder: 8.2.0
      validations:
          required: true
    - type: input
      id: laravel-version
      attributes:
        label: Laravel Version
        description: What version of Laravel are you running? Please be as specific as possible
        placeholder: 9.0.0
      validations:
        required: true
    - type: dropdown
      id: operating-systems
      attributes:
        label: Which operating systems does with happen with?
        description: You may select more than one.
        multiple: true
        options:
        - macOS
        - Windows
        - Linux
    - type: textarea
      id: notes
      attributes:
          label: Notes
          description: Use this field to provide any other notes that you feel might be relevant to the issue.
      validations:
          required: false


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Ask a question
    url: https://github.com/GuavaCZ/calendar/discussions/new?category=q-a
    about: Ask the community for help
  - name: Request a feature
    url: https://github.com/GuavaCZ/calendar/discussions/new?category=ideas
    about: Share ideas for new features
  - name: Report a security issue
    url: https://github.com/GuavaCZ/calendar/security/policy
    about: Learn how to notify us for sensitive bugs


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Create a feature request
title: "[Request]: "
body:
    - type: textarea
      id: feature-request
      attributes:
          label: What feature would you like to add?
          description: Please describe the new feature as thoroughly as possible.
      validations:
          required: true

    - type: textarea
      id: notes
      attributes:
          label: Notes
          description: Use this field to provide any other notes that you feel might be relevant to the issue.
      validations:
          required: false


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

If you discover any security related issues, please email office@guava.cz instead of using the issue tracker.


================================================
FILE: .github/workflows/phpstan.yml
================================================
name: PHPStan

on:
  push:
    paths:
      - '**.php'
      - 'phpstan.neon.dist'

jobs:
  phpstan:
    name: phpstan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          coverage: none

      - name: Install composer dependencies
        uses: ramsey/composer-install@v3

      - name: Run PHPStan
        run: ./vendor/bin/phpstan --error-format=github


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  push:
    branches:
      - master
      - main
      - beta
      - alpha
      - ?.[xX]
      - ?.?.[xX]

permissions:
  contents: write # to be able to publish a GitHub release
  issues: write # to be able to comment on released issues
  pull-requests: write # to be able to comment on released pull requests
  id-token: write # to enable use of OIDC for npm provenance

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    permissions:
      contents: write # to be able to publish a GitHub release
      issues: write # to be able to comment on released issues
      pull-requests: write # to be able to comment on released pull requests
      id-token: write # to enable use of OIDC for npm provenance
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Fix PHP code style issues
        uses: aglipanci/laravel-pint-action@2.5

      - name: Commit changes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: "style: fix code styling"

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "lts/*"

      - name: Install dependencies
        run: npm clean-install

      - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
        run: npm audit signatures

      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release


================================================
FILE: .github/workflows/run-tests.yml
================================================
name: run-tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-latest, windows-latest]
        php: ['8.1', '8.2', '8.3', '8.4', '8.5']
        laravel: ['10.*', '13.*']
        stability: [prefer-lowest, prefer-stable]
        include:
          - laravel: 10.*
            testbench: 8.*
            carbon: ^2.63
        exclude:
          - laravel: 13.*
            php: '8.1'
          - laravel: 13.*
            php: '8.2'

    name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
          coverage: none

      - name: Setup problem matchers
        run: |
          echo "::add-matcher::${{ runner.tool_cache }}/php.json"
          echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

      - name: Install dependencies
        run: |
          composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update
          composer update --${{ matrix.stability }} --prefer-dist --no-interaction

      - name: List Installed Dependencies
        run: composer show -D

      - name: Execute tests
        run: vendor/bin/pest --ci


================================================
FILE: .gitignore
================================================
.idea
.phpunit.cache
build
composer.lock
coverage
phpunit.xml
phpstan.neon
testbench.yaml
vendor
node_modules


================================================
FILE: .gitmodules
================================================
[submodule "demo"]
	path = demo
	url = https://github.com/GuavaCZ/calendar-demo
	branch = main


================================================
FILE: .releaserc.json
================================================
{
    "plugins": [
        [
            "@semantic-release/commit-analyzer",
            {
                "preset": "conventionalcommits",
                "releaseRules": [
                    {
                        "scope": "no-release",
                        "release": false
                    }
                ]
            }
        ],
        [
            "@semantic-release/release-notes-generator",
            {
                "preset": "conventionalcommits",
                "presetConfig": {
                    "types": [
                        {
                            "type": "feat",
                            "section": "Features"
                        },
                        {
                            "type": "fix",
                            "section": "Bug Fixes"
                        },
                        {
                            "type": "refactor",
                            "section": "Refactor"
                        },
                        {
                            "type": "docs",
                            "section": "Documentation"
                        },
                        {
                            "type": "chore",
                            "section": "Chore"
                        },
                        {
                            "type": "style",
                            "section": "Style"
                        },
                        {
                            "type": "perf",
                            "section": "Performance"
                        },
                        {
                            "type": "test",
                            "section": "Tests"
                        }
                    ]
                }
            }
        ],
        "@semantic-release/github"
    ],
    "branches": [
        "+([0-9])?(.{+([0-9]),x}).x",
        "main",
        "master",
        {
            "name": "beta",
            "prerelease": true
        },
        {
            "name": "alpha",
            "prerelease": true
        }
    ],
    "tagFormat": "${version}"
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to `calendar` will be documented in this file.


================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) Guava <lukas.frey@guava.cz>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
![calendar Banner](https://github.com/GuavaCZ/calendar/raw/main/docs/images/banner.jpg)


# Adds support for vkurko/calendar to Filament PHP.

[![Latest Version on Packagist](https://img.shields.io/packagist/v/guava/calendar.svg?style=flat-square)](https://packagist.org/packages/guava/calendar)
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/guava/calendar/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/GuavaCZ/calendar/actions?query=workflow%3Arun-tests+branch%3Amain)
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/guava/calendar/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/GuavaCZ/calendar/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain)
[![Total Downloads](https://img.shields.io/packagist/dt/guava/calendar.svg?style=flat-square)](https://packagist.org/packages/guava/calendar)

This package adds support for [vkurko/calendar](https://github.com/vkurko/calendar) (free, open-source alternative to FullCalendar) to your FilamentPHP panels.

It allows you to create a widget with a calendar with support for **multiple** models and even resources you can group your events into. For example, you could have lessons (events) that are held in different rooms (resources).

## Version compatibility
| Filament version | Plugin version |
| ---------------- |:--------------:| 
| 3.x              | 1.x            |
| 4.x              | 2.x            |
| 5.x              | 3.x            |

## Showcase
![Showcase 01](https://github.com/GuavaCZ/calendar/raw/main/docs/images/showcase_01.png)
![Showcase 02](https://github.com/GuavaCZ/calendar/raw/main/docs/images/showcase_02.png)



https://github.com/user-attachments/assets/fc7828ab-ccd2-4252-942a-9679af1e7687

<video width="320" height="240" controls>
  <source src="https://github.com/user-attachments/assets/fc7828ab-ccd2-4252-942a-9679af1e7687" type="video/mp4">
</video>


<video width="320" height="240" controls>
  <source src="https://github.com/GuavaCZ/calendar/raw/main/docs/images/demo_preview.mp4" type="video/mp4">
</video>

https://github.com/user-attachments/assets/a4460084-e8a8-4b1b-9ccd-4d887895155b


![Resources Screenshot 01](https://github.com/GuavaCZ/calendar/raw/main/docs/images/resources_screenshot_01.png)

<video width="320" height="240" controls>
  <source src="https://github.com/GuavaCZ/calendar/raw/main/docs/images/context_menu_preview.mp4" type="video/mp4">
</video>

https://github.com/user-attachments/assets/a2641b40-9cbd-4c40-b360-7621caa86c40

<video width="320" height="240" controls>
  <source src="https://github.com/GuavaCZ/calendar/raw/main/docs/images/context_menu_preview_2.mp4" type="video/mp4">
</video>


https://github.com/user-attachments/assets/4996cc6a-7cee-4c7d-976a-60d3a4368f76


<video width="320" height="240" controls>
  <source src="https://github.com/GuavaCZ/calendar/raw/main/docs/images/no_events_context_menu.mp4" type="video/mp4">
</video>

https://github.com/user-attachments/assets/7c2537d5-8acf-459f-a9a8-be02d4018448


## Support us

Your support is key to the continual advancement of our plugin. We appreciate every user who has contributed to our journey so far.

While our plugin is available for all to use, if you are utilizing it for commercial purposes and believe it adds significant value to your business, we kindly ask you to consider supporting us through GitHub Sponsors. This sponsorship will assist us in continuous development and maintenance to keep our plugin robust and up-to-date. Any amount you contribute will greatly help towards reaching our goals. Join us in making this plugin even better and driving further innovation.

## Installation

You can install the package via composer:

```bash
composer require guava/calendar
```

Make sure to publish the package assets using:

```bash
php artisan filament:assets
```

Finally, make sure you have a **custom filament theme** (read [here](https://filamentphp.com/docs/4.x/styling/overview#creating-a-custom-theme) how to create one) and add the following to your **theme.css** file:

This ensures that the CSS is properly built:
```css
@source '../../../../vendor/guava/calendar/resources/**/*';
```

This is optional but highly recommended as it will apply styles to better fit with the (default) filament theme:
```css
@import '../../../../vendor/guava/calendar/resources/css/theme.css';
```

The paths might be a little bit different if your theme.css is located in a non-standard path. Adjust accordingly.

## Usage

# Creating the calendar Widget
First you need to create a custom widget and extend the `CalendarWidget` class. Make sure to remove the `view` property from the generated widget class!

Either use the artisan command or simply create an empty class and extend `CalendarWidget`:
```bash
php artisan make:filament-widget
```

The widget class should look like this:
```php
use \Guava\Calendar\Filament\CalendarWidget;

class MyCalendarWidget extends CalendarWidget
{
}
```

Add the widget like a regular widget to any filament page you like, such as your `Dashboard`.

Congrats! You now have a working calendar in filament!

## Customizing the calendar view
By default, we show the `DayGridMonth` view. You can customize the view by overriding the `calendarView` property on the widget class:

```php
use Guava\Calendar\Enums\CalendarViewType;

protected CalendarViewType $calendarView = CalendarViewType::ResourceTimeGridWeek;
```

The `CalendarViewType` enum contains all available views that you can use.

## Adding events
You have probably noticed that your calendar is still empty.

To add events, override the `getEvents` method, described in more detail below.

```php
protected function getEvents(FetchInfo $info): Collection | array | Builder {}
```

FetchInfo is a ValueObject we provide which type hints all parameters that are made available to you. This helps you scope the query to only retrieve models which are visible in the view.

To learn more about FetchInfo and other Value Objects, please refer to the Value Object documentation.

There are two distinct ways on how to provide Events to the calendar. Which way you use depends on your personal preference and the source of your events.

### 1. From Eloquent
> [!IMPORTANT]
> Make sure that the Model class implements `Eventable`. Without it we are not able to map your model into a calendar event.
> 
> For more information, please refer to the [Calendar Events section](#calendar-events).

In the majority of cases, you will most likely want to display your eloquent models as events inside the calendar.

The easiest way to do that is to just return your Eloquent Query, and we will handle the rest.

```php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Guava\Calendar\ValueObjects\FetchInfo;

protected function getEvents(FetchInfo $info): Collection | array | Builder
{
    // The simplest way:
    return Foo::query();
    
    // You probably want to query only visible events:
    return Foo::query()
        ->whereDate('ends_at', '>=', $info->start)
        ->whereDate('starts_at', '<=', $info->end);
        
   // If you need to display multiple types of models,
   // you will need to combine the results of each
   // query builder manually:
   return collect()
       ->push(...Foo::query()->get())
       ->push(...Bar::query()->get())
   ;   
}
```

### 2. From Array/Collection

Sometimes, however, you might want to display events from an API or an array.

To do that, you can return an array or collection of `CalendarEvent` objects directly.

```php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Guava\Calendar\ValueObjects\CalendarEvent;
use Guava\Calendar\ValueObjects\FetchInfo;

protected function getEvents(FetchInfo $info): Collection | array | Builder
{
    return [
        CalendarEvent::make()
            ->title('My first calendar')
            ->start(now())
            ->end(now()->addHours(2)),
    ];
}
```

## Calendar events
As seen above, the calendar expects `CalendarEvent` objects, which are configurable ValueObjects that help you build the required calendar object.

For this purpose, we have an `Eventable` interface which you can add to **any class** that you want to be able to display as an event in your calendars.

In most cases, you will add this interface to your **eloquent models**, but you are not limited to it. Feel free to add it to any class you want.

Here is an example:

```php
use Guava\Calendar\Contracts\Eventable;
use Guava\Calendar\ValueObjects\CalendarEvent;

class Foo extends Model implements Eventable
{
    // ...
    
    // This is where you map your model into a calendar object
    public function toCalendarEvent(): CalendarEvent
    {
        // For eloquent models, make sure to pass the model to the constructor
        return CalendarEvent::make($this)
            ->title($this->name)
            ->start($this->starts_at)
            ->end($this->ends_at);
    }
}
```

Notice that the model instance is passed to the `Event` constructor in the `make` method. This sets the `key` and `model` properties to the calendar object, so it can be used to trigger actions.

This is a crucial step, if you forget to add this, we will not be able to identify the model when the event is interacted with (for example, when clicked).

### Configuration
The `CalendarEvent` takes all available options like the underlying calendar package, for more info [read here](https://github.com/vkurko/calendar?tab=readme-ov-file#event-object).

Below is a list of available methods on the calendar object:

#### Setting the title
Sets the title of the event that is rendered in the calendar.
```php
CalendarEvent::make()->title('My event');
```

To output Html in the title pass in a `HtmlString` or other class that implements `Htmlable` :

```php
CalendarEvent::make()
->title(new HtmlString('<b>My Event</b>'));
```


#### Customizing the start/end date
Sets the start or end date (and time) of the calendar in the calendar.
```php
CalendarEvent::make()
    ->start(today())
    ->end(today()->addDays(3));
```

#### Making the calendar all-day
Sets whether the calendar is an all-day calendar or not.
```php
CalendarEvent::make()->allDay();
```

#### Customizing the background / text color
Sets the background color of the calendar (by default, it is the primary color of your filament panel).
```php
CalendarEvent::make()
->backgroundColor('#ff0000')
->textColor('#ffffff');
```

#### Customizing Event Styles

You can add custom styles to your calendar elements by using the styles method. This method accepts an array where each entry can be a CSS style declaration. The styles will be directly applied to the calendar element in the view. You can define styles in three ways:

- As a key-value pair where the key is the CSS property and value is the condition under which the style should apply.
- As a key-value pair where the key is the CSS property and the value is directly the CSS value.
- As a single string for static styles that always apply.

Here's how you can use it:

```php
CalendarEvent::make()->styles([
    'color: red' => true,            // Applies the style if the condition (true) is met
    'background-color' => '#ffff00', // Directly applies the background color
    'font-size: 12px'                // Always applies this font size
]);
```

##### Usage Notes:

- The first format ('color: red' => true) is useful for conditional styling based on dynamic conditions. For instance, changing the text color based on an calendar's type or status.

- The second format ('background-color' => '#ffff00') is straightforward for applying styles where the values do not depend on conditions.
- The third format ('font-size: 12px') is used when the style does not require any condition and is always applied to the calendar.
This flexibility allows you to easily customize the appearance of events based on dynamic conditions or predefined settings.

#### Customizing Event Classes

Following the same pattern as with the styles property, it is possible to inject custom classes into the Event element using the `classNames` or `classes` property.

Here's how you can use it:

```php
CalendarEvent::make()->classNames([
    'class-1',            
    'class-2' => true  // Applies the class if the condition (true) is met
]);
```

##### Usage Notes:

- The second format ('class-2' => true) is useful for conditional classes based on dynamic conditions.

#### Customizing the display
By default, events are rendered as `blocks`. This is when the display is set to `auto`, which it is by default. You can also change the calendar to be rendered as a background calendar, which then fills the whole date cell. To do so, you can set `display` to `background` on the calendar:

This doesn't always work though, it only works on all day events and in specific views. If the `background` calendar is unsupported, the calendar will not be rendered at all.

```php
CalendarEvent::make()
->display('background') // or 'auto'
->displayAuto() // short-hand for ->display('auto')
->displayBackground(); // short-hand for ->display('background')
```

#### Setting the action on click
This sets the action that should be mounted when the calendar is clicked. It can be any name of a filament action you defined in your widget, such as `edit` or `view`.

By default, all `CalendarWidget` classes already include a `view` and `edit` action.

```php
CalendarEvent::make()->action('edit');
```

#### Set the model and record key
To mount the action with the correct record, we need to pass the model type and primary key of the record.

The model is also required if you want to display multiple types of events and have each be rendered differently (see customizing calendar content).

```php
$record = MyModel::find(1);
// 1. variant
CalendarEvent::make($record);

// 2. variant
CalendarEvent::make()
    ->model($record::class)
    ->key($record->getKey());
```

#### Linking to resources
If you use resources in your calendar, you need to tell your events which resource(s) they belong to.

To do this, pass an array of resource IDs to the event:
```php
use Guava\Calendar\ValueObjects\CalendarEvent;
CalendarEvent::make()
    ->resourceId('foo') // Pass a single resource ID, you can repeat this call multiple times
    ->resourceIds(['bar', 'baz']); // Pass multiple resource IDs at once
```

#### Passing custom data
You can pass any custom data to the event that you wish:
```php
CalendarEvent::make()
->extendedProp('foo', 'bar')
// or
->extendedProps(['baz' => 'qux', 'quux' => 'corge']);
```

## Adding Resources
If you configure your calendar to use a `resource` view, you will need to also define which resources to display in the calendar.

Similarly to events, There are two distinct ways on how to provide Resources to the calendar.

### 1. From Eloquent

> [!IMPORTANT]
> Make sure that the Model class implements `Resourceable`. Without it, we are not able to map your model into a calendar resource.
>
> For more information, please refer to the [Calendar Resources section](#calendar-resources).

In the majority of cases, you will most likely want to display your eloquent models as resources inside the calendar.

The easiest way to do that is to just return your Eloquent Query, and we will handle the rest.

```php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

public function getResources(): Collection | array| Builder
{
    return [
        Bar::query()
    ];
}
```

### 2. From Array/Collection

Sometimes, however, you might want to display resources from an API or an array.

To do that, you can return an array or collection of `CalendarResource` objects directly.

```php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Guava\Calendar\ValueObjects\CalendarResource;
use Guava\Calendar\ValueObjects\FetchInfo;

protected function getResources(): Collection | array | Builder
{
    return [
        CalendarResource::make('baz') // This has to be unique ID
            ->title('My resource'),
    ];
}
```

### Link events to resources
Now we successfully display resources in the calendar, but we still need to add the events to their corresponding resource.

To do this, update your `CalendarEvent` objects (either directly in the `getEvents` method or in your classes implementing `Eventable`, depending on which approach you chose) and include the resource IDs it belongs to.

Please refer to the [Calendar Event: linking to resources section](#linking-to-resources) to learn how to do it.

## Calendar Resources

As seen above, the calendar expects `CalendarResource` objects, which are configurable ValueObjects that help you build the required calendar object.

For this purpose, we have a `Resourceable` interface which you can add to **any class** that you want to be able to display as a resource in your calendars.

In most cases, you will add this interface to your **eloquent models**, but you are not limited to it. Feel free to add it to any class you want.

Here is an example:

```php
use Guava\Calendar\Contracts\Resourceable;
use Guava\Calendar\ValueObjects\CalendarResource;

class Bar extends Model implements Resourceable
{
    // ...
    
    // This is where you map your model into a calendar resource object
    public function toCalendarResource(): CalendarResource
    {
        return CalendarResource::make('my-unique-id')
            ->title($this->name);
    }
}
```

### Configuration
The `CalendarResource` takes all available options like the underlying calendar package, for more info [read here](https://github.com/vkurko/calendar?tab=readme-ov-file#resource-object).

Below is a list of available methods on the calendar object:

#### Setting the title
Sets the title of the resource that is rendered in the calendar.
```php
CalendarResource::make()->title('My resource');
```

#### Setting the event background color
Sets the default background color of the resource's events
```php
CalendarResource::make()->eventBackgroundColor('#FF0000');
```

#### Setting the event text color
Sets the default text color of the resource's events
```php
CalendarResource::make()->eventTextColor('#FFFFFF');
```

#### Passing custom data
You can pass any custom data to the resource that you wish:
```php
CalendarResource::make()
    ->extendedProp('foo', 'bar')
    // or
    ->extendedProps(['baz' => 'qux', 'quux' => 'corge']);
```

## Available Methods
The calendar widget class itself contains a few useful methods.

### Refresh events
If you need to trigger a refresh of the events in the calendar, you can call `refreshRecords()` on the widget.

```php
$this->refreshRecords();
```

### Refresh resources
If you need to trigger a refresh of the resources in the calendar, you can call `refreshResources()` on the widget.

```php
$this->refreshResources();
```

### Set Option
To change any calendar option during runtime, you can use the `setOption()` method on the widget.

For example, to programmatically change the date, you can use:
```php
$this->setOption('date', today()->addDay()->toIso8601String());
```

## Customization
Now that we know how to create a working calendar widget, we can learn how to customize the calendar to our liking.

The calendar widget contains a bunch of properties and methods that you can override in order to customize the widget.

In this section we will go through each of these options.

### Calendar view
The calendar comes with a variety of views. You can the calendar view per widget by overriding the `calendarView` property:

```php
use Guava\Calendar\Enums\CalendarViewType;

protected CalendarViewType $calendarView = CalendarViewType::ListWeek;
```

Check the `CalendarViewType` enum for a list of available views.

### Locale
By default, the calendar will use your app's locale.

The underlying calendar package doesn't support locales as a combination of language and region/country code, so locales such as `fr_CA` or `en_US` become invalid.

We attempt to resolve this by only using the first language part of the locale. If you still run into any issues with the localization, you can override the calendar's locale manually using the `locale` property:

```php
protected ?string $locale = 'en';
```

### First Day
By default, the calendar will use `Monday` as the first day.

You can customize this by overriding the `firstDay` property:
```php
use Carbon\WeekDay;

protected WeekDay $firstDay = WeekDay::Sunday;
```

### Day Max Events
Determines the maximum number of stacked event levels for a given day in the dayGrid view.

If there are too many events, a link like +2 more is displayed.

Currently, only a boolean value is supported. When set to true, it limits the number of events to the height of the day cell. When set to false (default) there is no limit.
     
```php
protected bool $dayMaxEvents = true;
```

### Use Filament Timezone

> [!CAUTION]
> While we still recommend setting this property to true, it **does** come with some potential side effects that you need to keep in mind. Keep reading to learn more.

The underlying Event Calendar does **not support** working with timezones. Thus, everything the user sees in their calendar is rendered in their **local browser time**.

This can cause confusion when adding interactivity to your calendar (such as editing events or creating events using filament modals), because filament will display the time **not** in the user's local browser time, but in the **app's timezone** (as configured in config/app.php).

However, as of Filament v4, a `FilamentTimezone` can be configured which will make Filament automatically convert between the Apps timezone setting and your filament timezone.  This allows you to store users preferred timezone and convert it back and forth when displaying it to the user, for example via a DatePicker.

To learn more about the `FilamentTimezone` setting, please refer to the [Filament documentation](https://filamentphp.com/docs/4.x/forms/date-time-picker#timezones). 

If you want your calendar to use the same timezone as configured via `FilamentTimezone`, you can use the `useFilamentTimezone` property to do so:

```php
protected bool $useFilamentTimezone = true;
```

To achieve this, we intercept dates sent from / to the calendar and override the timezone with the filament's timezone.

However, please keep in mind that this currently comes with a few side effects, as we are not able to override all dates / times used internally by the calendar.

For example, if you enable the `now indicator`, it will still use the user's local browser time. We are trying to find a solution to fix this.

### Heading
By default, the Heading displays the content of the `translations.heading` key (by default, it just says `Calendar`).

You can customize the heading of your calendar widget by overriding the `$heading` property or `getHeading` method:

```php
// Setting to null will disable the heading completely
protected string | HtmlString | bool | null $heading = null;

// Or to render HTML, you can override the method directly and return a HtmlString
public function getHeading(): string|HtmlString
{
    return  new HtmlString('<div>some html</div>');
}
```

## Interactivity

By now you should have a perfectly fine and working calendar. However, it is still very static - you can view your events, but there is no way to interact with them.

The calendar supports many ways to interact with, which will be described below individually.

### Actions
> [!CAUTION]
> Actions have no default authorization. This means, anyone can use any action.
> 
> Please check the [Authorization section](#authorization) to learn how to authorize actions.

Before you read about the different ways to add interactions to your calendar, you need to understand how actions in the calendar work.

Actions used within the Calendar context need the `CalendarAction` trait to work properly.

We provide a few drop-in replacements of the regular Filament actions that already implement everything necessary:

- CreateAction
- ViewAction
- EditAction
- DeleteAction

Whenever you want to use one of these actions, **make sure** you use the actions from our package. All they do is extend the regular filament action and add a few important setUp calls.

So, instead of using `Filament\Actions\CreateAction` you would use `Guava\Calendar\Filament\Actions\CreateAction`.

#### Defining actions
Every action you use in the calendar should be defined as a public method in the widget class, just as usual when adding an [Action to a Livewire component](https://filamentphp.com/docs/4.x/components/action#adding-the-action) in filament.

`View`, `Edit` and `Delete` actions are already present for you. You do not need to add them anymore.

For create actions, you still need to create them, since for each model a different create action needs to be added. There is a helper method available for you to help with this.

For example, to add a `createFooAction` (where `Foo` is a model in your app), you would add this method:

```php
use Guava\Calendar\Filament\Actions\CreateAction;

public function createFooAction(): CreateAction
{
    // You can use our helper method
    return $this->createAction(Foo::class);
    
    // Or you can add it manually, both variants are equivalent:
    return CreateAction::make('createFoo')
        ->model(Foo::class);
}
```

Both variants are equal, and it's just up to your personal preference which one you want to use.

#### Mounting actions
whenever you want to mount an action programmatically within a calendar context, such as in the `onDateClick` method (more on this later), you can use the `mountAction` method. 

```php
protected function onDateClick(DateClickInfo $info): void {
    $this->mountAction('createFoo');
}
```

In the background, we pass a few more arguments to the mount method.
Thanks to that, you can type hint the contextual info in your actions:

```php
use Guava\Calendar\Enums\Context;
use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\ValueObjects\DateClickInfo;
use Guava\Calendar\ValueObjects\DateSelectInfo;

public function createFooAction(): CreateAction
{
    return $this->createAction(Foo::class)
        ->mountUsing(function (?ContextualInfo $info) {
            // You can now access contextual info from the calendar using the $info argument 
            if ($info instanceof DateClickInfo) {
                // do something on date click
            }
            
            // Both comparison checks are equal, but instanceof is better for IDE help
            if ($info->getContext() === Context::DateSelect) {
                // do something on date select
            }
        })
        // You could also type hint each contextual info directly:
        ->mountUsing(fn(?DateClickInfo $dateClick, ?DateSelectInfo $dateSelect))
    ;
}
```

#### Accessing context information
As seen above, we provide various contextual information for you when using calendar actions:

| Parameter         | Description                                                                            |
|-------------------|----------------------------------------------------------------------------------------|
| Context           | The current context enum or null if not in calendar context                            |
| DateClickInfo     | If in the DateClick context, it will contain the DateClickInfo, otherwise null         |
| DateSelectInfo    | If in the DateSelect context, it will contain the DateSelectInfo, otherwise null       |
| EventClickInfo    | If in the EventClick context, it will contain the EventClickInfo, otherwise null       |
| NoEventsClickInfo | If in the NoEventsClick context, it will contain the NoEventsClickInfo, otherwise null |

You simply need to type hint the parameter correctly and it will be injected for you if available.

These are not only limited to `mountUsing`, almost all action methods will have access to these.

For example, to conditionally hide an action in the DateClick context:
```php
use Guava\Calendar\Enums\Context;

$this->createAction(Foo::class)
    ->hidden(function (?ContextualInfo $info) {
        return $info->getContext() === Context::DateClick;
    });
```

### Schemas

The `create`, `view` and `edit` actions should work out of the box and use the correct schemas.

We attempt to guess your Resource and reuse the appropriate schema: `Create` and `Edit` actions will use your **Form Schema**, while `View` actions will reuse your **Infolist Schema** or fall-back to the **Form Schema** if no Infolist is present.

But sometimes, you might want to customize the Schema that will be used in your Calendar Modals.

You have a few options available:
- Stick to auto discovery, in which case you are ready to go :-),
- if your calendar works with a single model only or you reuse the same schema for multiple models, you can implement the `schema` or `defaultSchema` method,
- or you can implement a schema per model

#### Implementing a default schema

If you only work with a single model, or you want to share the same schema across multiple models, you can implement the `schema` or `defaultSchema` method (both are equivalent) in your calendar widget:

```php
public function defaultSchema(Schema $schema): Schema
{
    return $schema->components([
        // ...
    ]);
}
```

#### Implementing a schema for a specific model

If you need to set a specific schema for a model, you have two options:
- Define a method with any name you want and add the `#[CalendarSchema()]` attribute,
- or define a method in the format `camelCaseModelNameSchema`, such as `fooBarSchema`

```php
// Variant 1
public function fooBarSchema(Schema $schema): Schema
{
    return $schema->components([
        // ...
    ]);
}

// Variant 2
use Guava\Calendar\Attributes\CalendarSchema;

#[CalendarSchema(FooBar::class)]
public function baz(Schema $schema): Schema
{
    return $schema->components([
        // ...
    ]);
}
```

### Date Click

A date click event is triggered when a date cell is clicked in the calendar.

To handle date clicks, first enable them by overriding the `dateClickEnabled` property:

```php
protected bool $dateClickEnabled = true;
```

Now date clicks are enabled and a request will be sent to livewire each time a date cell is clicked.

But by default, nothing happens, and each date click will be silently ignored.

You can now choose to either:
- implement your own logic
- or use our context menu feature

#### Implementing your own logic

If you want to take full control over what happens when a date cell is clicked, override the `onDateClick` method and implement your own custom logic:

```php
use Guava\Calendar\ValueObjects\DateClickInfo;

protected function onDateClick(DateClickInfo $info): void
{
    // Validate the data and handle the event
    // For example, you might want to mount a create action
    $this->mountAction('createFoo');
}
```

#### Using the context menu feature
Another option is to use our context menu feature. When enabled, a context menu will be rendered at your mouse cursor when you click a date cell, which you can populate with actions.

To use the context menu feature, all you need to do is implement the `getDateClickContextMenuActions` method:

```php
protected function getDateClickContextMenuActions(): array
{
    return [
        $this->createFooAction(),
        $this->createBarAction(),
        // Any other action you want
    ];
}
```

The context menu has a higher priority, so if it returns a non-empty array, it will always take precedence over your custom handler.

### Date Select

Similarly, a date select event is triggered when a date cell is dragged to create a selection in the calendar.

To handle date selects, first enable them by overriding the `dateSelectEnabled` property:

```php
protected bool $dateSelectEnabled = true;
```

Now date selects are enabled and a request will be sent to livewire each time a date selection is made.

But by default, nothing happens, and each date select will be silently ignored.

You can now choose to either:
- implement your own logic
- or use our context menu feature

#### Implementing your own logic

If you want to take full control over what happens when a date selection is made, override the `onDateSelect` method and implement your own custom logic:

```php
use Guava\Calendar\ValueObjects\DateSelectInfo;

protected function onDateSelect(DateSelectInfo $info): void
{
    // Validate the data and handle the event
    // For example, you might want to mount a create action
    $this->mountAction('createFoo');
}
```

#### Using the context menu feature
Another option is to use our context menu feature. When enabled, a context menu will be rendered at your mouse cursor when you complete your date selection, which you can populate with actions.

To use the context menu feature, all you need to do is implement the `getDateSelectContextMenuActions` method:

```php
protected function getDateSelectContextMenuActions(): array
{
    return [
        $this->createFooAction(),
        $this->createBarAction(),
        // Any other action you want
    ];
}
```

The context menu has a higher priority, so if it returns a non-empty array, it will always take precedence over your custom handler.

### Event Click

An event click event is triggered when an event is clicked in the calendar.

To handle event clicks, first enable them by overriding the `eventClickEnabled` property:

```php
protected bool $eventClickEnabled = true;
```

Now event clicks are enabled and a request will be sent to livewire each time an event is clicked.

By default, a view action will be mounted with prefilled information about the event.

You can set the default click action by overriding the `defaultEventClickAction` property of the widget. This simply needs to be the name of an action that you can freely define in your widget, like regular Filament actions:

```php
protected ?string $defaultEventClickAction = 'edit'; // view and edit actions are provided by us, but you can choose any action you want, even your own custom ones
```

You can now choose to either:
- keep the default behavior,
- implement your own logic
- or use our context menu feature

#### Implementing your own logic

If you want to take full control over what happens when an event is clicked, override the `onEventClick` method and implement your own custom logic:

```php
use Illuminate\Database\Eloquent\Model;
use Guava\Calendar\ValueObjects\EventClickInfo;

protected function onEventClick(EventClickInfo $info, Model $event, ?string $action = null): void
{
    // Validate the data and handle the event click
    // $event contains the clicked event record
    // you can also access it via $info->record
}
```

#### Using the context menu feature
Another option is to use our context menu feature. When enabled, a context menu will be rendered at your mouse cursor when you click an event, which you can populate with actions.

To use the context menu feature, all you need to do is implement the `getEventClickContextMenuActions` method:

```php
protected function getEventClickContextMenuActions(): array
{
    return [
        $this->viewAction(),
        $this->editAction(),
        $this->deleteAction(),
    ];
}
```

The context menu has a higher priority, so if it returns a non-empty array, it will always take precedence over your custom handler.

### No Events Click
> [!NOTE]  
> This has affect only in list views.

A no events click event is triggered when a list view has no events to display and the calendar content was clicked.

To handle no events clicks, first enable them by overriding the `noEventsClickEnabled` property:

```php
protected bool $noEventsClickEnabled = true;
```

Now no events clicks are enabled and a request will be sent to livewire each time a click is made.

But by default, nothing happens, and each click will be silently ignored.

You can now choose to either:
- implement your own logic
- or use our context menu feature

#### Implementing your own logic

If you want to take full control over what happens when a click is made, override the `onNoEventsClick` method and implement your own custom logic:

```php
use Guava\Calendar\ValueObjects\NoEventsClickInfo;

protected function onNoEventsClick(NoEventsClickInfo $info): void
{
    // Validate the data and handle the event
    // For example, you might want to mount a create action
    $this->mountAction('createFoo');
}
```

#### Using the context menu feature
Another option is to use our context menu feature. When enabled, a context menu will be rendered at your mouse cursor when you click, which you can populate with actions.

To use the context menu feature, all you need to do is implement the `getNoEventsClickContextMenuActions` method:

```php
protected function getNoEventsClickContextMenuActions(): array
{
    return [
        $this->createFooAction(),
        $this->createBarAction(),
        // Any other action you want
    ];
}
```

The context menu has a higher priority, so if it returns a non-empty array, it will always take precedence over your custom handler.

### Event Resize

Callback function that is triggered when you finish resizing an event in your calendar.

To handle the callback, first enable it by overriding the `eventResizeEnabled` property:

```php
protected bool $eventResizeEnabled = true;
```

Now it is enabled and a request will be sent to livewire after you complete the resize of an event.

#### Implementing your own logic

> [!IMPORTANT]  
> Notice that unlike the other callbacks, this callback returns a boolean value.
> 
> This is used to control whether the event resize should be reverted visually on the frontend or not.

To handle the callback, override the `onEventResize` method and implement your own custom logic:

```php
use Illuminate\Database\Eloquent\Model;
use Guava\Calendar\ValueObjects\EventResizeInfo;

protected function onEventResize(EventResizeInfo $info, Model $event): bool
{
    // Validate the data and handle the event
    // Most likely you will want to update the event with the new start /end dates to persist the resize in the database

    return true;
}
```

### Event Drag & Drop

Callback function that is triggered when you finish dragging and drop an event to a date cell in your calendar.

To handle the callback, first enable it by overriding the `eventDragEnabled` property:

```php
protected bool $eventDragEnabled = true;
```

Now it is enabled and a request will be sent to livewire after you drop the event to a date cell in the calendar.

#### Implementing your own logic

> [!IMPORTANT]  
> Notice that unlike the other callbacks, this callback returns a boolean value.
>
> This is used to control whether the event should be reverted visually to it's original position on the frontend or not.

To handle the callback, override the `onEventDrop` method and implement your own custom logic:

```php
use Illuminate\Database\Eloquent\Model;
use Guava\Calendar\ValueObjects\EventDropInfo;

protected function onEventDrop(EventDropInfo $info, Model $event): bool
{
     // Access the updated dates using getter methods
    $newStart = $info->event->getStart();
    $newEnd = $info->event->getEnd();
      // Update the event with the new start/end dates to persist the drag & drop
    $event->update([
        'start_time' => $newStart,
        'end_time' => $newEnd,
    ]);
     // Return true to accept the drop and keep the event in the new position
    return true;
    
}
```

### Dates Set

When the date range of the calendar was originally set or changed by clicking the previous/next buttons, changing the view, manipulating the current date via the API, etc. a Dates Set event is triggered.

To handle the Dates Set callback, first enable it by overriding the `datesSetEnabled` property:

```php
protected bool $datesSetEnabled = true;
```

Now it is enabled and a request will be sent to livewire each time the calendar date range is changed (or initially set).

#### Implementing your own logic

To handle the callback, override the `onDatesSet` method and implement your own custom logic:

```php
use Guava\Calendar\ValueObjects\DatesSetInfo;

protected function onDatesSet(DatesSetInfo $info): void
{
    // Validate the data and handle the event
    // For example, you might want to store the date range in a cookie or session
    // to remember the date range across page refreshes
}
```

### View Did Mount

Callback function that is triggered right after the view has been added to the DOM.

To handle the callback, first enable it by overriding the `viewDidMountEnabled` property:

```php
protected bool $viewDidMountEnabled = true;
```

Now it is enabled and a request will be sent to livewire right after the calendar view has been added to the DOM.

#### Implementing your own logic

To handle the callback, override the `onViewDidMount` method and implement your own custom logic:

```php
use Guava\Calendar\ValueObjects\ViewDidMountInfo;

protected function onViewDidMount(ViewDidMountInfo $info): void
{
    // Validate the data and handle the event
    // For example, you might want to store the date range in a cookie or session
    // to remember the date range across page refreshes
}
```

## Custom Event Content
By default, we use the default view from the calendar package. However, you are able to use your own content.

To keep things performant, the blade view is rendered **once** on the server and then re-used for every event. Thus, you **cannot** access the calendar event data from the server side via Blade or Laravel, or do any server-side operations.

However, each event is wrapped in an alpine component, which exposes the event data that you can freely use using [AlpineJS](https://alpinejs.dev/).

If you only have one type of events or events that render the same way, you can simply return a view or a HtmlString from the `defaultEventContent` or `eventContent` method:

```php
use Illuminate\Support\HtmlString;

protected function eventContent(): HtmlString|string
{
    // return a blade view
    return view('calendar.event');
    
    // return a HtmlString
    return new HtmlString('<div>My event</div>');
}
```

Example of the `calendar.event` view blade file: 
```bladehtml
<div class="flex flex-col items-start">
    <span x-text="event.title"></span>
    <template x-for="user in event.extendedProps.users">
        <span x-text="user.name"></span>
    </template>
</div>
```

If you want to render events differently based on their model type, you can implement an Event Content method for each model, by using the `CalendarEventContent` attribute or by using a specific naming convention for the method - `camelCaseModelNameEventContent` such as `fooEventContent` (where Foo is your model):
```php
use Illuminate\Support\HtmlString;
use Guava\Calendar\Attributes\CalendarEventContent;

// Variant 1.
#[CalendarEventContent(Foo::class)]
protected function eventContentForFoo(): HtmlString|string
{
    return view('calendar.foo-model-event');
}

// Variant 2.
protected function barEventContent(): HtmlString|string
{
    return view('calendar.bar-model-event');
}
```

Both variants are equal, it's up to your personal preference which one you want to use.

## Custom Resource Label Content
By default, we use the default view from the calendar package. However, you are able to use your own content.

To keep things performant, the blade view is rendered **once** on the server and then re-used for every resource. Thus, you **cannot** access the calendar resource data from the server side via Blade or Laravel, or do any server-side operations.

However, each resource is wrapped in an alpine component, which exposes the resource data that you can freely use using [AlpineJS](https://alpinejs.dev/).

If you only have one type of resources or resources that render the same way, you can simply return a view or a HtmlString from the `defaultResourceLabelContent` or `resourceLabelContent` method:

```php
use Illuminate\Support\HtmlString;

protected function resourceLabelContent(): HtmlString|string
{
    // return a blade view
    return view('calendar.resource');
    
    // return a HtmlString
    return new HtmlString('<div>My resource</div>');
}
```

Example of the `calendar.resource` view blade file:
```bladehtml
<div class="flex flex-col items-start">
    <span x-text="resource.title"></span>
</div>
```

If you want to render resources differently based on their model type, you can implement a Resource Label Content method for each model, by using the `CalendarResourceLabelContent` attribute or by using a specific naming convention for the method - `camelCaseModelNameResourceLabelContent` such as `fooResourceLabelContent` (where Foo is your model):
```php
use Illuminate\Support\HtmlString;
use Guava\Calendar\Attributes\CalendarResourceLabelContent;

// Variant 1.
#[CalendarResourceLabel(Foo::class)]
protected function resourceLabelContentForFoo(): HtmlString|string
{
    return view('calendar.foo-model-resource');
}

// Variant 2.
protected function barResourceLabelContent(): HtmlString|string
{
    return view('calendar.bar-model-resource');
}
```

Both variants are equal, it's up to your personal preference which one you want to use.

## Authorization

By default, everyone can use all actions! The package does not handle authorization, this is your responsibility.

Since we use regular filament actions, adding authorizations is a breeze. 

For example to add authorization to the createTaskAction:

```php
use Guava\Calendar\Filament\Actions\CreateAction;

public function createFooAction(): CreateAction
{
    return $this->createAction(Foo::class)
        ->authorize('create', Foo::class)
        // At this point, it will authorize against the FooPolicy
        //
        // However, you might want to give the user some feedback:
        ->authorizationNotification()
        // Now it will send a notification with the response message from your policy
        //
        // For context menu actions, you can instead use:
        ->authorizationTooltip()
        // which will disable the action and show a tooltip with the response message
    ;
}
```

For detailed information, please follow the default filament documentation on how to add [authorization to actions](https://filamentphp.com/docs/4.x/actions/overview#authorization).

## Utility Classes
We provide various helper and utility classes to provide you with type hints for various arguments that are being passed from the calendar to your widget.

### CalendarViewType
This enum contains all available calendar views that you can use in your calendar widget.

### FetchInfo
When the calendar retrieves events, it provides you with a `FetchInfo` object which contains information about the current calendar view.

Use this to query only models that are visible in the current view.

| Property | Description                                                                                                     |
|--------|-----------------------------------------------------------------------------------------------------------------|
| start  | Start date of the range the calendar needs events for. Events before this date are not visible in the calendar. |
| end    | End date of the range the calendar needs events for. Events after this date are not visible in the calendar.    |

## Troubleshooting
### Context menu actions don't work
If you encounter issues with the context menu, make sure that the name of the action is unique across the whole widget. If there is another action with the same name, it might be mounted instead of the one you want.

### Record vs Event record
When working with resource widgets, `$record` is the record of the currently opened resource record, whereas `$eventRecord` is the record of the calendar event (during calendar actions, context menus, etc.).

## Security measures
Keep in mind that a lot of the data in this package comes from the client side JavaScript and could be tampered with. Always validate the data on the server side and never trust the data from the client side.

## Testing

```bash
composer test
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

## Security Vulnerabilities

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

## Credits
- [Lukas Frey](https://github.com/GuavaCZ)
- [All Contributors](../../contributors)
- Spatie - Our package skeleton is a modified version of [Spatie's Package Skeleton](https://github.com/spatie/package-skeleton-laravel)
- [vkurko/calendar](https://github.com/vkurko/calendar) - free, open-source alternative to FullCalendar
- [saade/filament-fullcalendar](https://github.com/saade/filament-fullcalendar) - heavy inspiration for this package

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.


================================================
FILE: bin/build.js
================================================
import esbuild from 'esbuild'

const isDev = process.argv.includes('--dev')

async function compile(options) {
    const context = await esbuild.context(options)

    if (isDev) {
        await context.watch()
    } else {
        await context.rebuild()
        await context.dispose()
    }
}

const defaultOptions = {
    define: {
        'process.env.NODE_ENV': isDev ? `'development'` : `'production'`,
    },
    bundle: true,
    mainFields: ['module', 'main'],
    platform: 'neutral',
    sourcemap: isDev ? 'inline' : false,
    sourcesContent: isDev,
    treeShaking: true,
    target: ['es2020'],
    minify: !isDev,
    plugins: [{
        name: 'watchPlugin',
        setup: function (build) {
            build.onStart(() => {
                console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
            })

            build.onEnd((result) => {
                if (result.errors.length > 0) {
                    console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors)
                } else {
                    console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
                }
            })
        }
    }],
}

compile({
    ...defaultOptions,
    entryPoints: ['./resources/js/calendar.js'],
    outfile: './dist/js/calendar.js',
})

compile({
    ...defaultOptions,
    entryPoints: ['./resources/js/calendar-context-menu.js'],
    outfile: './dist/js/calendar-context-menu.js',
})

compile({
    ...defaultOptions,
    entryPoints: ['./resources/js/calendar-event.js'],
    outfile: './dist/js/calendar-event.js',
})

// compile({
//     ...defaultOptions,
//     entryPoints: ['./resources/js/calendar-context-menu.js'],
//     outfile: './dist/js/calendar-context-menu.js',
// })
//
// compile({
//     ...defaultOptions,
//     entryPoints: ['./resources/js/event.js'],
//     outfile: './dist/js/event.js',
// })


================================================
FILE: composer.json
================================================
{
    "name": "guava/calendar",
    "description": "Adds support for vkurko/calendar to Filament PHP.",
    "keywords": [
        "Guava",
        "laravel",
        "calendar"
    ],
    "homepage": "https://github.com/GuavaCZ/calendar",
    "license": "MIT",
    "authors": [
        {
            "name": "Lukas Frey",
            "email": "lukas.frey@guava.cz",
            "role": "Developer"
        }
    ],
    "require": {
        "php": "^8.1|^8.2",
        "filament/filament": "^5.0",
        "illuminate/contracts": "^11.0|^12.0|^13.0",
        "spatie/laravel-package-tools": "^1.14.0"
    },
    "require-dev": {
        "laravel/pint": "^1.0",
        "nunomaduro/collision": "^7.8|^8.0",
        "nunomaduro/larastan": "^2.0.1",
        "orchestra/testbench": "^9.0|^10.0|^11.0",
        "pestphp/pest": "^2.36|^4.4",
        "pestphp/pest-plugin": "^2.1.1|^4.0",
        "pestphp/pest-plugin-arch": "^2.0|^4.0",
        "pestphp/pest-plugin-laravel": "^2.0|^4.1",
        "phpstan/extension-installer": "^1.1",
        "phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
        "phpstan/phpstan-phpunit": "^1.0|^2.0"
    },
    "autoload": {
        "psr-4": {
            "Guava\\Calendar\\": "src/",
            "Guava\\Calendar\\Database\\Factories\\": "database/factories/"
        },
        "files": [
            "src/helpers.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Guava\\Calendar\\Tests\\": "tests/",
            "Workbench\\App\\": "workbench/app/"
        }
    },
    "scripts": {
        "post-autoload-dump": "@composer run prepare",
        "clear": "@php vendor/bin/testbench package:purge-calendar --ansi",
        "prepare": "@php vendor/bin/testbench package:discover --ansi",
        "build": [
            "@composer run prepare",
            "@php vendor/bin/testbench workbench:build --ansi"
        ],
        "start": [
            "Composer\\Config::disableProcessTimeout",
            "@composer run build",
            "@php vendor/bin/testbench serve"
        ],
        "analyse": "vendor/bin/phpstan analyse",
        "test": "vendor/bin/pest",
        "test-coverage": "vendor/bin/pest --coverage",
        "format": "vendor/bin/pint"
    },
    "config": {
        "sort-packages": true,
        "allow-plugins": {
            "pestphp/pest-plugin": true,
            "phpstan/extension-installer": true
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Guava\\Calendar\\CalendarServiceProvider"
            ]
        }
    },
    "minimum-stability": "beta",
    "prefer-stable": false
}


================================================
FILE: config/calendar.php
================================================
<?php

// config for Guava/Calendar
return [

];


================================================
FILE: database/factories/ModelFactory.php
================================================
<?php

namespace Guava\Calendar\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/*
class ModelFactory extends Factory
{
    protected $model = YourModel::class;

    public function definition()
    {
        return [

        ];
    }
}
*/


================================================
FILE: database/migrations/create_calendar_table.php.stub
================================================
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('calendar_table', function (Blueprint $table) {
            $table->id();

            // add fields

            $table->timestamps();
        });
    }
};


================================================
FILE: dist/js/calendar-context-menu.js
================================================
function d({getContextMenuActionsUsing:o}){return{open:!1,size:{width:0,height:0},position:{x:0,y:0},mountData:{},context:null,actions:[],isLoading:!1,onCloseCallback:null,menu:{"x-show"(){return this.open},"x-bind:style"(){return`
                    position: absolute;
                    z-index: 40;
                    top: ${this.position.y}px;
                    left: ${this.position.x}px;
                `},"x-on:click.away"(){this.closeMenu()}},init:async function(){let t=this.$el.querySelector('[x-bind="menu"]');this.size={width:t.offsetWidth,height:t.offsetHeight},this.$el.addEventListener("calendar--open-menu",e=>this.openMenu(e))},loadActions:async function(t,e={}){this.isLoading=!0,this.actions=[],o(t,e).then(n=>{this.actions=n}).finally(()=>this.isLoading=!1)},openMenu:async function(t,e=null){this.$nextTick(()=>{let n=t.clientX,i=t.clientY,s=t.pageX,c=t.pageY,a=n+this.size.width>window.innerWidth?n+this.size.width-window.innerWidth:0,h=i+this.size.height>window.innerHeight?i+this.size.height-window.innerHeight:0;if(this.position.x=s-a,this.position.y=c-h,this.open=!0,e){let l=e.getAttribute("data-event-id");document.querySelectorAll(`.ec-event[data-event-id="${l}"]`).forEach(u=>u.classList.add("gu-context-menu-open"))}})},closeMenu:function(){this.open=!1,document.querySelectorAll(".ec-event.gu-context-menu-open").forEach(t=>t.classList.remove("gu-context-menu-open")),this.onCloseCallback&&this.onCloseCallback()}}}export{d as default};


================================================
FILE: dist/js/calendar-event.js
================================================
function o({event:t,timeText:r,view:d,hasContextMenu:n}){return{event:t,contextMenu:null,init:function(){n&&this.initializeContextMenu(),this.$el.setAttribute("data-event-id",t.id),this.$el.addEventListener("mouseenter",()=>{document.querySelectorAll(`.ec-event[data-event-id="${t.id}"]`).forEach(e=>{e.classList.add("gu-hover")})}),this.$el.addEventListener("mouseleave",()=>{document.querySelectorAll(`.ec-event[data-event-id="${t.id}"]`).forEach(e=>{e.classList.remove("gu-hover")})})},initializeContextMenu:function(){let e=document.querySelector("[calendar-context-menu]");this.contextMenu=Alpine.$data(e)},onClick:function(e){if(e.event.extendedProps.url){window.open(this.event.extendedProps.url,this.event.extendedProps.url_target??"_blank");return}let i={event:e.event,view:e.view,tzOffset:-new Date().getTimezoneOffset()};if(n){this.contextMenu.loadActions("eventClick",i),this.contextMenu.openMenu(e.jsEvent,this.$el);return}this.$wire.onEventClickJs(i)}}}export{o as default};


================================================
FILE: dist/js/calendar.js
================================================
function M({view:c="dayGridMonth",locale:v="en",firstDay:f=1,dayMaxEvents:w=!1,eventContent:l=null,eventClickEnabled:m=!1,eventDragEnabled:i=!1,eventResizeEnabled:d=!1,noEventsClickEnabled:h=!1,dateClickEnabled:D=!1,dateSelectEnabled:o=!1,datesSetEnabled:E=!1,viewDidMountEnabled:S=!1,eventAllUpdatedEnabled:O=!1,hasDateClickContextMenu:g=null,hasDateSelectContextMenu:p=null,hasEventClickContextMenu:z=null,hasNoEventsClickContextMenu:x=null,resources:C=null,resourceLabelContent:r=null,theme:u=null,options:$={},eventAssetUrl:y}){return{init:function(){let n=this.mountCalendar();window.addEventListener("calendar--refresh",()=>{n.refetchEvents()}),this.$wire.on("calendar--set",e=>{n.setOption(e.key,e.value)})},mountCalendar:function(){return EventCalendar.create(this.$el.querySelector("[data-calendar]"),this.getSettings())},getSettings:function(){let n={view:c,locale:v,firstDay:f,dayMaxEvents:w,eventSources:[{events:e=>this.$wire.getEventsJs({...e,tzOffset:-new Date().getTimezoneOffset()})}],resources:C,selectable:o,eventStartEditable:i,eventDurationEditable:d,dayCellFormat:e=>e.getDate().toString()};return l!==null&&(n.eventContent=e=>{let t=e.event.extendedProps.model,s=l[t]??l._default;if(s!==void 0)return{html:s}}),r!==null&&(n.resourceLabelContent=e=>{let t=e.resource.extendedProps.model,s=r[t]??r._default;if(s!==void 0)return{html:this.wrapContent(s,e)}}),D&&(n.dateClick=e=>{let t={date:e.date,dateStr:e.dateStr,allDay:e.allDay,view:e.view,resource:e.resource,tzOffset:-new Date().getTimezoneOffset()};g?this.openContextMenu(e.jsEvent,t,"dateClick"):this.$wire.onDateClickJs(t)}),o&&(n.select=e=>{let t={start:e.start,startStr:e.startStr,end:e.end,endStr:e.endStr,allDay:e.allDay,view:e.view,resource:e.resource,tzOffset:-new Date().getTimezoneOffset()};p?this.openContextMenu(e.jsEvent,t,"dateSelect"):this.$wire.onDateSelectJs(t)}),E&&(n.datesSet=e=>{this.$wire.onDatesSetJs({start:e.start,startStr:e.startStr,end:e.end,endStr:e.endStr,view:e.view,tzOffset:-new Date().getTimezoneOffset()})}),m&&(n.eventClick=e=>{Alpine.$data(e.el).onClick(e)}),n.eventResize=async e=>{let t=e.event.durationEditable,s=d;t!==void 0&&(s=t),s&&await this.$wire.onEventResizeJs({event:e.event,oldEvent:e.oldEvent,endDelta:e.endDelta,view:e.view,tzOffset:-new Date().getTimezoneOffset()}).then(a=>{a===!1&&e.revert()})},n.eventDrop=async e=>{let t=e.event.startEditable,s=i;t!==void 0&&(s=t),s&&await this.$wire.onEventDropJs({event:e.event,oldEvent:e.oldEvent,oldResource:e.oldResource,newResource:e.newResource,delta:e.delta,view:e.view,tzOffset:-new Date().getTimezoneOffset()}).then(a=>{a===!1&&e.revert()})},n.eventDidMount=e=>{e.el.setAttribute("x-load"),e.el.setAttribute("x-load-src",y),e.el.setAttribute("x-data",`calendarEvent({
                    event: ${JSON.stringify(e.event)},
                    timeText: "${e.timeText}",
                    view: ${JSON.stringify(e.view)},
                    hasContextMenu: ${z},
                })`)},h&&(n.noEventsClick=e=>{let t={view:e.view,tzOffset:-new Date().getTimezoneOffset()};x?this.openContextMenu(e.jsEvent,t,"noEventsClick"):this.$wire.onNoEventsClickJs(t)}),S&&(n.viewDidMount=e=>{this.$wire.onViewDidMountJs({view:e.view,tzOffset:-new Date().getTimezoneOffset()})}),O&&(n.eventAllUpdated=e=>{this.$wire.onEventAllUpdatedJs({view:e.view,tzOffset:-new Date().getTimezoneOffset()})}),u&&(n.theme=function(e){return{...e,...u}}),{...n,...$}},wrapContent:function(n,e){let t=document.createElement("div");return t.innerHTML=n,t.setAttribute("x-data",JSON.stringify(e)),t.classList.add("w-full"),t.outerHTML},openContextMenu:function(n,e,t){let s=document.querySelector("[calendar-context-menu]"),a=Alpine.$data(s);a.loadActions(t,e),a.openMenu(n)}}}export{M as default};


================================================
FILE: package.json
================================================
{
    "devDependencies": {
        "@event-calendar/core": "^4.5.0",
        "conventional-changelog-conventionalcommits": "^7.0.2",
        "esbuild": "^0.21.3",
        "semantic-release": "^22.0.5"
    },
    "type": "module",
    "dependencies": {
        "@awcodes/alpine-floating-ui": "^3.6.3",
        "@event-calendar/day-grid": "^3.0.0",
        "@event-calendar/time-grid": "^3.0.0"
    }
}


================================================
FILE: phpstan-baseline.neon
================================================


================================================
FILE: phpstan.neon.dist
================================================
includes:
    - phpstan-baseline.neon
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:
    level: 4
    paths:
        - src
        - config
        - database
    tmpDir: build/phpstan
    checkOctaneCompatibility: true
    checkModelProperties: true
    checkMissingIterableValueType: false



================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
    backupGlobals="false"
    bootstrap="vendor/autoload.php"
    colors="true"
    processIsolation="false"
    stopOnFailure="false"
    executionOrder="random"
    failOnWarning="true"
    failOnRisky="true"
    failOnEmptyTestSuite="true"
    beStrictAboutOutputDuringTests="true"
    cacheDirectory=".phpunit.cache"
    backupStaticProperties="false"
>
    <testsuites>
        <testsuite name="Guava Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <coverage>
        <report>
            <html outputDirectory="build/coverage"/>
            <text outputFile="build/coverage.txt"/>
            <clover outputFile="build/logs/clover.xml"/>
        </report>
    </coverage>
    <logging>
        <junit outputFile="build/report.junit.xml"/>
    </logging>
    <source>
        <include>
            <directory suffix=".php">./src</directory>
        </include>
    </source>
</phpunit>


================================================
FILE: pint.json
================================================
{
    "preset": "laravel",
    "rules": {
        "method_argument_space": true,
        "multiline_whitespace_before_semicolons": {
            "strategy": "new_line_for_chained_calls"
        },
        "types_spaces": {
            "space": "single"
        },
        "concat_space": false
    }
}


================================================
FILE: resources/css/theme.css
================================================
.ec {
    --ec-list-day-bg-color: white;
    --ec-today-bg-color: var(--color-50);

    /*--ec-text-color: var(--gray-500);*/

    --ec-event-bg-color: var(--bg);
    --ec-event-text-color: var(--text);
    --ec-bg-event-color: var(--color-50);
    --ec-border-color: var(--gray-200);
    --ec-button-bg-color: var(--bg);
    --ec-button-text-color: var(--text);
    --ec-button-active-bg-color: var(--hover-bg);
    --ec-button-active-text-color: var(--hover-text);
}
.dark .ec {
    --ec-list-day-bg-color: var(--gray-900);
    --ec-today-bg-color: color-mix(in oklab, var(--color-500) 20%, transparent);

    /*--ec-text-color: var(--gray-200);*/

    --ec-event-bg-color: var(--dark-bg);
    --ec-event-text-color: var(--dark-text);
    --ec-border-color: rgba(255, 255, 255, 0.1);
    --ec-button-bg-color: var(--dark-bg);
    --ec-button-text-color: var(--dark-text);
    --ec-button-active-bg-color: var(--dark-hover-bg);
    --ec-button-active-text-color: var(--dark-hover-text);
}


.ec-button {
    @apply rounded-lg border-0 text-sm py-2 px-3 font-medium;

    &:disabled {
        @apply opacity-70 bg-[var(--ec-button-bg-color)] text-[var(--ec-button-text-color)];
    }
}

/* This adds the "divider" between buttons in a button group */
.ec-button-group {
    & .ec-button {
        @apply me-px;
        &:not(:first-child) {
            @apply border-s border-s-white dark:border-s-gray-950;
        }
    }
}

.ec-title {
    @apply text-base font-semibold text-gray-950 dark:text-white;
}

.ec-header {
    .ec-day {
        @apply font-semibold text-gray-950 dark:text-white;
    }
}

.ec-day-grid,
.ec-time-grid {
    & .ec-header {
        @apply rounded-ss-lg rounded-se-lg;
    }

    & .ec-body {
        @apply rounded-es-lg rounded-ee-lg;
    }
}
.ec-timeline {
    & .ec-sidebar {
        @apply rounded-ss-lg rounded-es-lg;
    }
    & .ec-main .ec-header {
        @apply rounded-se-lg;
    }

    & .ec-main .ec-body {
        @apply rounded-ee-lg;
    }
}
.ec-list {
    & .ec-body {
        @apply rounded-lg;
    }
}

/*.ec-event {*/
/*    @apply bg-[var(--bg)] dark:bg-[var(--dark-bg)];*/
/*    & .ec-event-body {*/
/*        @apply text-[var(--text)] dark:text-[(var(--dark-text))];*/
/*    }*/
/*}*/

/* Only apply these settings in day and time grid views (not in list views) */
.ec-day-grid,
.ec-time-grid {

    & .ec-time {
        @apply text-gray-400 dark:text-gray-400;
    }
    & .ec-event {
        /* We need to apply hover using our custom class since one event is split into multiple elements if wrapped inside multiple calendar rows */
        &.gu-hover {
            /*@apply bg-red-400;*/
            @apply text-[var(--hover-text)] dark:text-[var(--dark-hover-text)] bg-[var(--hover-bg)] dark:bg-[var(--dark-hover-bg)];
        }

        &.gu-context-menu-open {
            @apply text-[var(--hover-text)] dark:text-[var(--dark-hover-text)] bg-[var(--hover-bg)] dark:bg-[var(--dark-hover-bg)];
        }
    }
}


================================================
FILE: resources/js/calendar-context-menu.js
================================================
export default function calendarContextMenu({
                                                getContextMenuActionsUsing,
                                            }) {
    return {
        open: false,

        size: {
            width: 0,
            height: 0,
        },
        position: {
            x: 0,
            y: 0,
        },
        mountData: {},
        context: null,
        actions: [],
        isLoading: false,
        onCloseCallback: null,

        menu: {
            ['x-show']() {
                return this.open
            },
            ['x-bind:style']() {
                return `
                    position: absolute;
                    z-index: 40;
                    top: ${this.position.y}px;
                    left: ${this.position.x}px;
                `
            },
            ['x-on:click.away']() {
                this.closeMenu()
            }
        },

        init: async function () {
            const menu = this.$el.querySelector('[x-bind="menu"]')
            this.size = {
                width: menu.offsetWidth,
                height: menu.offsetHeight,
            }

            this.$el.addEventListener('calendar--open-menu', (event) => this.openMenu(event))
        },

        loadActions: async function (context, data = {}) {
            this.isLoading = true
            this.actions = []
            getContextMenuActionsUsing(context, data)
                .then((actions) => {
                    this.actions = actions
                })
                .finally(() => this.isLoading = false)
        },

        openMenu: async function (event, eventElement = null) {
            this.$nextTick(() => {
                const clientX = event.clientX;
                const clientY = event.clientY;
                const pageX = event.pageX;
                const pageY = event.pageY;

                const offsetX = clientX + this.size.width > window.innerWidth
                    ? clientX + this.size.width - window.innerWidth
                    : 0;
                const offsetY = clientY + this.size.height > window.innerHeight
                    ? clientY + this.size.height - window.innerHeight
                    : 0;

                this.position.x = pageX - offsetX;
                this.position.y = pageY - offsetY;
                this.open = true;

                if (eventElement) {
                    const eventId = eventElement.getAttribute('data-event-id')

                    document.querySelectorAll(`.ec-event[data-event-id="${eventId}"]`).forEach(
                        el => el.classList.add('gu-context-menu-open')
                    )
                }
            });
        },

        closeMenu: function () {
            this.open = false;

            document.querySelectorAll('.ec-event.gu-context-menu-open').forEach(
                event => event.classList.remove('gu-context-menu-open')
            )
            if (this.onCloseCallback) {
                this.onCloseCallback();
            }
        }
    }
}


================================================
FILE: resources/js/calendar-event.js
================================================
export default function calendarEvent({
                                          event,
                                          timeText,
                                          view,
                                          hasContextMenu,
                                      }
) {
    return {
        event,
        contextMenu: null,

        init: function () {
            if (hasContextMenu) {
                this.initializeContextMenu()
            }
            this.$el.setAttribute('data-event-id', event.id)

            this.$el.addEventListener('mouseenter', () => {
                document.querySelectorAll(`.ec-event[data-event-id="${event.id}"]`).forEach(el => {
                    el.classList.add('gu-hover')
                })
            })

            this.$el.addEventListener('mouseleave', () => {
                document.querySelectorAll(`.ec-event[data-event-id="${event.id}"]`).forEach(el => {
                    el.classList.remove('gu-hover')
                })
            })
            // Preloading
            // this.$el.addEventListener('mouseenter', () => {
            //     this.contextMenu.loadActions(this.event)
            // })
        },

        initializeContextMenu: function () {
            const element = document.querySelector('[calendar-context-menu]')
            this.contextMenu = Alpine.$data(element)
        },

        /**
         * Called when an event is clicked, if event clicking is enabled in the calendar.
         * @param info
         */
        onClick: function (info) {
            if (info.event.extendedProps.url) {
                window.open(
                    this.event.extendedProps.url,
                    this.event.extendedProps.url_target ?? '_blank'
                )
                return
            }

            const data = {
                event: info.event,
                view: info.view,
                tzOffset: -new Date().getTimezoneOffset()
            }

            if (hasContextMenu) {
                this.contextMenu.loadActions('eventClick', data)
                this.contextMenu.openMenu(
                    info.jsEvent,
                    this.$el
                )
                return
            }

            this.$wire.onEventClickJs(data)
        },
    }
}


================================================
FILE: resources/js/calendar.js
================================================
export default function calendar({
                                     view = 'dayGridMonth',
                                     locale = 'en',
                                     firstDay = 1,
                                     dayMaxEvents = false,
                                     eventContent = null,
                                     eventClickEnabled = false,
                                     eventDragEnabled = false,
                                     eventResizeEnabled = false,
                                     noEventsClickEnabled = false,
                                     dateClickEnabled = false,
                                     dateSelectEnabled = false,
                                     datesSetEnabled = false,
                                     viewDidMountEnabled = false,
                                     eventAllUpdatedEnabled = false,
                                     hasDateClickContextMenu = null,
                                     hasDateSelectContextMenu = null,
                                     hasEventClickContextMenu = null,
                                     hasNoEventsClickContextMenu = null,
                                     resources = null,
                                     resourceLabelContent = null,
                                     theme = null,
                                     options = {},
                                     eventAssetUrl,
                                 }
) {
    return {

        init: function () {
            const ec = this.mountCalendar()

            window.addEventListener('calendar--refresh', () => {
                ec.refetchEvents()
            })

            this.$wire.on('calendar--set', (data) => {
                ec.setOption(data.key, data.value)
            })
        },

        mountCalendar: function () {
            return EventCalendar.create(
                this.$el.querySelector('[data-calendar]'),
                this.getSettings(),
            )
        },

        getSettings: function () {
            let settings = {
                view: view,
                locale: locale,
                firstDay: firstDay,
                dayMaxEvents: dayMaxEvents,
                eventSources: [
                    {
                        events: (fetchInfo) => this.$wire.getEventsJs({
                            ...fetchInfo,
                            tzOffset: -new Date().getTimezoneOffset(),
                        })
                    }
                ],
                resources: resources,
                selectable: dateSelectEnabled,
                eventStartEditable: eventDragEnabled,
                eventDurationEditable: eventResizeEnabled,
                dayCellFormat: (date) => date.getDate().toString()
            }

            if (eventContent !== null) {
                settings.eventContent = (info) => {
                    const model = info.event.extendedProps.model
                    const content = eventContent[model] ?? eventContent['_default']

                    if (content === undefined) {
                        return undefined
                    }

                    return {
                        html: content,
                    }
                }
            }

            if (resourceLabelContent !== null) {
                settings.resourceLabelContent = (info) => {
                    const model = info.resource.extendedProps.model
                    const content = resourceLabelContent[model] ?? resourceLabelContent['_default']

                    if (content === undefined) {
                        return undefined
                    }

                    return {
                        html: this.wrapContent(content, info),
                    }
                };
            }

            if (dateClickEnabled) {
                settings.dateClick = (info) => {
                    const data = {
                        date: info.date,
                        dateStr: info.dateStr,
                        allDay: info.allDay,
                        view: info.view,
                        resource: info.resource,
                        tzOffset: -new Date().getTimezoneOffset()
                    }

                    if (hasDateClickContextMenu) {
                        this.openContextMenu(info.jsEvent, data, 'dateClick')
                    } else {
                        this.$wire.onDateClickJs(data)
                    }
                }
            }

            if (dateSelectEnabled) {
                settings.select = (info) => {
                    const data = {
                        start: info.start,
                        startStr: info.startStr,
                        end: info.end,
                        endStr: info.endStr,
                        allDay: info.allDay,
                        view: info.view,
                        resource: info.resource,
                        tzOffset: -new Date().getTimezoneOffset()
                    }

                    if (hasDateSelectContextMenu) {
                        this.openContextMenu(info.jsEvent, data, 'dateSelect')
                    } else {
                        this.$wire.onDateSelectJs(data)
                    }
                }
            }

            if (datesSetEnabled) {
                settings.datesSet = (info) => {
                    this.$wire.onDatesSetJs({
                        start: info.start,
                        startStr: info.startStr,
                        end: info.end,
                        endStr: info.endStr,
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    })
                }
            }

            if (eventClickEnabled) {
                settings.eventClick = (info) => {
                    const component = Alpine.$data(info.el)
                    component.onClick(info)
                }
            }

            settings.eventResize = async (info) => {
                const durationEditable = info.event.durationEditable
                let enabled = eventResizeEnabled

                if (durationEditable !== undefined) {
                    enabled = durationEditable
                }

                if (enabled) {
                    await this.$wire.onEventResizeJs({
                        event: info.event,
                        oldEvent: info.oldEvent,
                        endDelta: info.endDelta,
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    }).then((result) => {
                        if (result === false) {
                            info.revert()
                        }
                    })
                }
            };

            settings.eventDrop = async (info) => {
                const startEditable = info.event.startEditable
                let enabled = eventDragEnabled

                if (startEditable !== undefined) {
                    enabled = startEditable
                }

                if (enabled) {
                    await this.$wire.onEventDropJs({
                        event: info.event,
                        oldEvent: info.oldEvent,
                        oldResource: info.oldResource,
                        newResource: info.newResource,
                        delta: info.delta,
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    }).then((result) => {
                        if (result === false) {
                            info.revert()
                        }
                    })
                }
            }

            settings.eventDidMount = (info) => {
                info.el.setAttribute('x-load')
                info.el.setAttribute('x-load-src', eventAssetUrl)
                info.el.setAttribute('x-data', `calendarEvent({
                    event: ${JSON.stringify(info.event)},
                    timeText: "${info.timeText}",
                    view: ${JSON.stringify(info.view)},
                    hasContextMenu: ${hasEventClickContextMenu},
                })`)
            }

            if (noEventsClickEnabled) {
                settings.noEventsClick = (info) => {
                    const data = {
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    }

                    if (hasNoEventsClickContextMenu) {
                        this.openContextMenu(info.jsEvent, data, 'noEventsClick')
                    } else {
                        this.$wire.onNoEventsClickJs(data)
                    }
                }
            }

            if (viewDidMountEnabled) {
                settings.viewDidMount = (info) => {
                    this.$wire.onViewDidMountJs({
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    })
                }
            }

            if (eventAllUpdatedEnabled) {
                settings.eventAllUpdated = (info) => {
                    this.$wire.onEventAllUpdatedJs({
                        view: info.view,
                        tzOffset: -new Date().getTimezoneOffset()
                    })
                }
            }

            if (theme) {
                settings.theme = function (defaultTheme) {
                    return {
                        ...defaultTheme,
                        ...theme
                    }
                }
            }

            return {
                ...settings,
                ...options,
            }
        },

        wrapContent: function (content, info) {
            let container = document.createElement('div')
            container.innerHTML = content

            // Add alpine data and classes
            container.setAttribute('x-data', JSON.stringify(info))
            container.classList.add('w-full')

            // Get the modified HTML
            return container.outerHTML
        },

        openContextMenu: function (jsEvent, data, context) {
            const element = document.querySelector('[calendar-context-menu]')
            const contextMenu = Alpine.$data(element)
            contextMenu.loadActions(context, data)
            contextMenu.openMenu(jsEvent)
        }
    }
}


================================================
FILE: resources/lang/cs/translations.php
================================================
<?php

return [
    'heading' => 'Kalendář',
];


================================================
FILE: resources/lang/de/translations.php
================================================
<?php

return [
    'heading' => 'Kalender',
];


================================================
FILE: resources/lang/en/translations.php
================================================
<?php

return [
    'heading' => 'Calendar',
];


================================================
FILE: resources/lang/fr/translations.php
================================================
<?php

return [
    'heading' => 'Calendrier',
];


================================================
FILE: resources/views/.gitkeep
================================================


================================================
FILE: resources/views/components/context-menu.blade.php
================================================
@php
    use Filament\Support\Facades\FilamentAsset;
    use Guava\Calendar\Enums\Context;

    use function Filament\Support\generate_loading_indicator_html;
@endphp

<div x-ignore
     x-load
     x-load-src="{{ FilamentAsset::getAlpineComponentSrc('calendar-context-menu', 'guava/calendar') }}"
     x-data="calendarContextMenu({
            getContextMenuActionsUsing: async (context, data) => {
                return await $wire.getContextMenuActionsUsing(context, data)
            },
         })"
     calendar-context-menu
     {{--     x-teleport="body"--}}
     class="absolute top-0 left-0 z-30"
>
    <div x-bind="menu"
         x-transition:enter-start="fi-opacity-0" x-transition:leave-end="fi-opacity-0"
        @class([
           "fi-dropdown-panel absolute w-screen max-w-xs divide-y divide-gray-100 rounded-lg bg-white shadow-lg ring-1 ring-gray-950/5 transition dark:divide-white/5 dark:bg-gray-800 dark:ring-white/10",
       ])
    >
        {{--        <div class="w-full flex items-center justify-center p-2">{{generate_loading_indicator_html()}}</div>--}}
        <div wire:loading.flex wire:target="getContextMenuActionsUsing" class="w-full flex items-center justify-center p-2">{{generate_loading_indicator_html()}}</div>
        <x-filament::dropdown.list>
            {{--                    <div x-html="renderedActions"></div>--}}
            <template x-for="action in actions">
                <div x-html="action"></div>
            </template>
            {{--                    @foreach ($dateClickContextMenuActions as $action)--}}
            {{--                        {{ $action }}--}}
            {{--                    @endforeach--}}
        </x-filament::dropdown.list>
        <div>
            {{--                <x-filament::dropdown.list x-show="context == '{{Context::DateClick}}'">--}}
            {{--                    @foreach ($dateClickContextMenuActions as $action)--}}
            {{--                        {{ $action }}--}}
            {{--                    @endforeach--}}
            {{--                </x-filament::dropdown.list>--}}
            {{--                <x-filament::dropdown.list x-show="context == '{{Context::DateSelect}}'">--}}
            {{--                    @foreach ($dateSelectContextMenuActions as $action)--}}
            {{--                        {{ $action }}--}}
            {{--                    @endforeach--}}
            {{--                </x-filament::dropdown.list>--}}
            {{--            <x-filament::dropdown.list>--}}
            {{--                @foreach ($eventClickContextMenuActions as $action)--}}
            {{--                    {{ $action }}--}}
            {{--                @endforeach--}}
            {{--            </x-filament::dropdown.list>--}}
            {{--                <x-filament::dropdown.list x-show="context == '{{Context::NoEventsClick}}'">--}}
            {{--                    @foreach ($noEventsClickContextMenuActions as $action)--}}
            {{--                        {{ $action }}--}}
            {{--                    @endforeach--}}
            {{--                </x-filament::dropdown.list>--}}
        </div>
    </div>
</div>


================================================
FILE: resources/views/widgets/calendar-widget.blade.php
================================================
@php
    use Filament\Support\Facades\FilamentAsset;
    use Guava\Calendar\Enums\Context;
    use Filament\Support\Facades\FilamentColor;
    use Filament\Support\View\Components\ButtonComponent;
@endphp

<x-filament-widgets::widget>
    <x-filament::section
        :after-header="$this->getCachedHeaderActionsComponent()"
        :footer="$this->getCachedFooterActionsComponent()"
    >

        <style>
            .ec-event.ec-preview,
            .ec-now-indicator {
                z-index: 30;
            }
        </style>

        @if($heading = $this->getHeading())
            <x-slot name="heading">
                {{ $this->getHeading() }}
            </x-slot>
        @endif

        <div
            wire:ignore
            x-load
            x-load-src="{{ FilamentAsset::getAlpineComponentSrc('calendar', 'guava/calendar') }}"
            x-data="calendar({
                view: @js($this->getCalendarView()),
                locale: @js($this->getLocale()),
                firstDay: @js($this->getFirstDay()),
                dayMaxEvents: @js($this->getDayMaxEvents()),
                eventContent: @js($this->getEventContentJs()),
                eventClickEnabled: @js($this->isEventClickEnabled()),
                eventDragEnabled: @js($this->isEventDragEnabled()),
                eventResizeEnabled: @js($this->isEventResizeEnabled()),
                noEventsClickEnabled: @js($this->isNoEventsClickEnabled()),
                dateClickEnabled: @js($this->isDateClickEnabled()),
                dateSelectEnabled: @js($this->isDateSelectEnabled()),
                datesSetEnabled: @js($this->isDatesSetEnabled()),
                viewDidMountEnabled: @js($this->isViewDidMountEnabled()),
                eventAllUpdatedEnabled: @js($this->isEventAllUpdatedEnabled()),
                hasDateClickContextMenu: @js($this->hasContextMenu(Context::DateClick)),
                hasDateSelectContextMenu: @js($this->hasContextMenu(Context::DateSelect)),
                hasEventClickContextMenu: @js($this->hasContextMenu(Context::EventClick)),
                hasNoEventsClickContextMenu: @js($this->hasContextMenu(Context::NoEventsClick)),
                resources: @js($this->getResourcesJs()),
                resourceLabelContent: @js($this->getResourceLabelContentJs()),
                theme: @js($this->getTheme()),
                options: @js($this->getOptions()),
                eventAssetUrl: @js(FilamentAsset::getAlpineComponentSrc('calendar-event', 'guava/calendar')),
            })"
            @class(FilamentColor::getComponentClasses(ButtonComponent::class, 'primary'))
        >
            <div data-calendar></div>
            @if($this->hasContextMenu())
                <x-guava-calendar::context-menu/>
            @endif
        </div>
    </x-filament::section>
        <x-filament-actions::modals/>
</x-filament-widgets::widget>


================================================
FILE: src/Attributes/CalendarEventContent.php
================================================
<?php

namespace Guava\Calendar\Attributes;

use Illuminate\Database\Eloquent\Model;

#[\Attribute(\Attribute::TARGET_METHOD)]
class CalendarEventContent
{
    /**
     * @param  class-string<Model>  $model
     */
    public function __construct(public string $model) {}
}


================================================
FILE: src/Attributes/CalendarResourceLabelContent.php
================================================
<?php

namespace Guava\Calendar\Attributes;

use Illuminate\Database\Eloquent\Model;

#[\Attribute(\Attribute::TARGET_METHOD)]
class CalendarResourceLabelContent
{
    /**
     * @param  class-string<Model>  $model
     */
    public function __construct(public string $model) {}
}


================================================
FILE: src/Attributes/CalendarSchema.php
================================================
<?php

namespace Guava\Calendar\Attributes;

use Illuminate\Database\Eloquent\Model;

#[\Attribute(\Attribute::TARGET_METHOD)]
class CalendarSchema
{
    /**
     * @param  class-string<Model>  $model
     */
    public function __construct(public string $model) {}
}


================================================
FILE: src/CalendarPlugin.php
================================================
<?php

namespace Guava\Calendar;

use Filament\Contracts\Plugin;
use Filament\Panel;

class CalendarPlugin implements Plugin
{
    public function getId(): string
    {
        return 'guava-calendar';
    }

    public function register(Panel $panel): void {}

    public function boot(Panel $panel): void {}

    public static function make(): static
    {
        return app(static::class);
    }
}


================================================
FILE: src/CalendarServiceProvider.php
================================================
<?php

namespace Guava\Calendar;

use Filament\Support\Assets\AlpineComponent;
use Filament\Support\Assets\Css;
use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;

class CalendarServiceProvider extends PackageServiceProvider
{
    public function configurePackage(Package $package): void
    {
        /*
         * This class is a Package Service Provider
         *
         * More info: https://github.com/spatie/laravel-package-tools
         */
        $package
            ->name('guava-calendar')
            ->hasTranslations()
            ->hasViews()
        ;
    }

    public function packageBooted(): void
    {
        FilamentAsset::register(
            assets: [
                AlpineComponent::make(
                    'calendar',
                    __DIR__ . '/../dist/js/calendar.js',
                ),
                AlpineComponent::make(
                    'calendar-context-menu',
                    __DIR__ . '/../dist/js/calendar-context-menu.js',
                ),
                AlpineComponent::make(
                    'calendar-event',
                    __DIR__ . '/../dist/js/calendar-event.js',
                ),
                Css::make('calendar-styles', 'https://cdn.jsdelivr.net/npm/@event-calendar/build@5.5.1/dist/event-calendar.min.css'),
                Js::make('calendar-script', 'https://cdn.jsdelivr.net/npm/@event-calendar/build@5.5.1/dist/event-calendar.min.js'),
            ],
            package: 'guava/calendar'
        );
    }
}


================================================
FILE: src/Concerns/CalendarAction.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Contracts\HasCalendar;
use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\DateClickInfo;
use Guava\Calendar\ValueObjects\DateSelectInfo;
use Guava\Calendar\ValueObjects\EventClickInfo;
use Guava\Calendar\ValueObjects\NoEventsClickInfo;

trait CalendarAction
{
    protected function resolveDefaultClosureDependencyForEvaluationByType(string $parameterType): array
    {
        /** @var InteractsWithCalendar $livewire */
        $livewire = $this->getLivewire();

        // Action is used outside the calendar
        if (! ($livewire instanceof HasCalendar)) {
            return parent::resolveDefaultClosureDependencyForEvaluationByType($parameterType);
        }

        $expectedContext = match ($parameterType) {
            DateClickInfo::class => Context::DateClick,
            DateSelectInfo::class => Context::DateSelect,
            EventClickInfo::class => Context::EventClick,
            NoEventsClickInfo::class => Context::NoEventsClick,
            ContextualInfo::class => null,
            default => false,
        };

        if ($expectedContext !== false) {
            $contextInfo = $livewire->getCalendarContextInfo();

            return ($expectedContext === null || $contextInfo->getContext() === $expectedContext)
                ? [$contextInfo]
                : [null];
        }

        return parent::resolveDefaultClosureDependencyForEvaluationByType($parameterType);
    }
}


================================================
FILE: src/Concerns/CanRefreshCalendar.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait CanRefreshCalendar
{
    public function refreshRecords(): static
    {
        $this->dispatch('calendar--refresh');

        return $this;
    }

    public function refreshResources(): static
    {
        $this->setOption('resources', $this->getResourcesJs());

        return $this;
    }
}


================================================
FILE: src/Concerns/CanUseFilamentTimezone.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait CanUseFilamentTimezone
{
    protected bool $useFilamentTimezone = false;

    public function shouldUseFilamentTimezone(): bool
    {
        return $this->useFilamentTimezone;
    }
}


================================================
FILE: src/Concerns/HandlesDateClick.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\DateClickInfo;
use phpDocumentor\Reflection\Types\This;

trait HandlesDateClick
{
    /**
     * Sets whether clicking on a date cell should be enabled for the calendar or not.
     *
     * To enable date click, set this to true and override `onDateClick` to implement your logic.
     */
    protected bool $dateClickEnabled = false;

    /**
     * Implement your date click logic here.
     *
     * This method will only be fired when `$dateClickEnabled` is set to true.
     *
     * @param  DateClickInfo  $info  contains information about the clicked date cell
     */
    protected function onDateClick(DateClickInfo $info): void {}

    /**
     * Sets whether clicking on a date cell should be enabled for the calendar or not.
     *
     * To enable date click, return true and override `onDateClick` to implement your logic.
     */
    public function isDateClickEnabled(): bool
    {
        return $this->dateClickEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onDateClick` instead
     */
    public function onDateClickJs(array $data): void
    {
        // Check if date click is enabled
        if (! $this->isDateClickEnabled()) {
            return;
        }

        $this->setRawCalendarContextData(Context::DateClick, $data);

        $this->onDateClick($this->getCalendarContextInfo());
    }
}


================================================
FILE: src/Concerns/HandlesDateSelect.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\DateSelectInfo;

trait HandlesDateSelect
{
    /**
     * Sets whether selecting a date range should be enabled for the calendar or not.
     *
     * To enable date select, set this to true and override `onDateSelect` to implement your logic.
     */
    protected bool $dateSelectEnabled = false;

    /**
     * Implement your date select logic here.
     *
     * This method will only be fired when `$dateSelectEnabled` is set to true.
     *
     * @param  DateSelectInfo  $info  contains information about the selected date range
     */
    protected function onDateSelect(DateSelectInfo $info): void {}

    /**
     * Sets whether selecting a date range should be enabled for the calendar or not.
     *
     * To enable date select, return true and override `onDateSelect` to implement your logic.
     */
    public function isDateSelectEnabled(): bool
    {
        return $this->dateSelectEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onDateSelect` instead
     */
    public function onDateSelectJs(array $data): void
    {
        // Check if date select is enabled
        if (! $this->isDateSelectEnabled()) {
            return;
        }

        $this->setRawCalendarContextData(Context::DateSelect, $data);

        $this->onDateSelect($this->getCalendarContextInfo());
    }
}


================================================
FILE: src/Concerns/HandlesDatesSet.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\ValueObjects\DatesSetInfo;

trait HandlesDatesSet
{
    /**
     * Sets whether the Dates Set callback should be enabled or not.
     *
     * To enable the Dates Set callback, set this to true and override `onDatesSet` to implement your logic.
     */
    protected bool $datesSetEnabled = false;

    /**
     * Implement your dates set callback logic here.
     *
     * This method will only be fired when `$datesSetEnabled` is set to true.
     *
     * When enabled, this will be called when the date range of the calendar was originally set or changed by clicking the previous/next buttons, changing the view, manipulating the current date via the API, etc.
     *
     * @param  DatesSetInfo  $info  contains information about the current calendar view
     */
    protected function onDatesSet(DatesSetInfo $info): void {}

    /**
     * Sets whether the Dates Set callback be enabled or not.
     *
     * To enable the Dates Set callback, return true and override `onDatesSet` to implement your logic.
     */
    public function isDatesSetEnabled(): bool
    {
        return $this->datesSetEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onDatesSet` instead
     */
    public function onDatesSetJs(array $data): void
    {
        // Check if dates set is enabled
        if (! $this->isDatesSetEnabled()) {
            return;
        }

        $this->onDatesSet(new DatesSetInfo($data, $this->shouldUseFilamentTimezone()));
    }
}


================================================
FILE: src/Concerns/HandlesEventAllUpdated.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\ValueObjects\EventAllUpdatedInfo;

trait HandlesEventAllUpdated
{
    protected bool $eventAllUpdatedEnabled = false;

    protected function onEventAllUpdated(EventAllUpdatedInfo $info): void {}

    public function isEventAllUpdatedEnabled(): bool
    {
        return $this->eventAllUpdatedEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onEventAllUpdated()` instead
     */
    public function onEventAllUpdatedJs(array $info): void
    {
        // Check if dates set is enabled
        if (! $this->isEventAllUpdatedEnabled()) {
            return;
        }

        $this->onEventAllUpdated(new EventAllUpdatedInfo($info, $this->shouldUseFilamentTimezone()));
    }
}


================================================
FILE: src/Concerns/HandlesEventClick.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\EventClickInfo;
use Illuminate\Database\Eloquent\Model;

trait HandlesEventClick
{
    protected bool $eventClickEnabled = false;

    protected ?string $defaultEventClickAction = 'view';

    /**
     * @throws \Exception
     */
    protected function onEventClick(EventClickInfo $info, Model $event, ?string $action = null): void
    {
        // No action to trigger
        if (! $action) {
            return;
        }

        $this->mountAction($action);
    }

    public function isEventClickEnabled(): bool
    {
        return $this->eventClickEnabled;
    }

    public function getDefaultEventClickAction(): ?string
    {
        return $this->evaluate($this->defaultEventClickAction);
    }

    /**
     * @internal Do not override, internal purpose only. Use `onEventClick` instead
     */
    public function onEventClickJs(array $data = [], ?string $action = null): void
    {
        // Check if event click is enabled
        if (! $this->isEventClickEnabled()) {
            return;
        }

        $this->setRawCalendarContextData(Context::EventClick, $data);

        $action ??= $this->getRawCalendarContextData('event.extendedProps.action');
        $action ??= $this->getDefaultEventClickAction();

        // TODO: Similar to how Schemas work, allow users to define a method for each Event Model Type
        // TODO: using attributes. such as #[CalendarEventClick(Sprint::class)] above a method
        // TODO: such as onSprintEventClick would be only called for Sprint.
        $this->onEventClick($this->getCalendarContextInfo(), $this->getEventRecord(), $action);
    }

    // TODO: Might be worth looking into to automatically choose between view /edit action based on the permissions
    //
    //    protected function resolveDefaultEventClickAction() {
    //        foreach (['view', 'edit'] as $action) {
    //            $action = $this->getAction($action);
    //
    //            if (! $action) {
    //                continue;
    //            }
    //
    //            $action = clone $action;
    //
    //            $action->record($record);
    //            $action->getGroup()?->record($record);
    //
    //            if ($action->isHidden()) {
    //                continue;
    //            }
    //
    //            $url = $action->getUrl();
    //
    //            if (! $url) {
    //                continue;
    //            }
    //
    //            return $url;
    //        }
    //
    //    }
}


================================================
FILE: src/Concerns/HandlesEventDragAndDrop.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\EventDropInfo;
use Illuminate\Database\Eloquent\Model;

trait HandlesEventDragAndDrop
{
    protected bool $eventDragEnabled = false;

    // TODO: Add a default implementation
    // TODO: for that we need to add two methods to Eventable interface:
    // TODO: -> getStartAttribute()
    // TODO: -> getEndAttribute()
    // TODO: where the user needs to define which attributes is the start/end date
    // TODO: Then we can handle the update outselves by default
    protected function onEventDrop(EventDropInfo $info, Model $event): bool
    {
        return true;
    }

    public function isEventDragEnabled(): bool
    {
        return $this->eventDragEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onEventDrop()` instead
     */
    public function onEventDropJs(array $data): bool
    {
        // Check if event drag and drop is enabled
        if (! $this->isEventDragEnabled()) {
            return false;
        }

        $this->setRawCalendarContextData(Context::EventDragAndDrop, $data);

        return $this->onEventDrop($this->getCalendarContextInfo(), $this->getEventRecord());
    }
}


================================================
FILE: src/Concerns/HandlesEventResize.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\EventResizeInfo;
use Illuminate\Database\Eloquent\Model;

trait HandlesEventResize
{
    protected bool $eventResizeEnabled = false;

    // TODO: Add a default implementation
    // TODO: for that we need to add two methods to Eventable interface:
    // TODO: -> getStartAttribute()
    // TODO: -> getEndAttribute()
    // TODO: where the user needs to define which attributes is the start/end date
    // TODO: Then we can handle the update outselves by default
    protected function onEventResize(EventResizeInfo $info, Model $event): bool
    {
        return true;
    }

    public function isEventResizeEnabled(): bool
    {
        return $this->eventResizeEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onEventResize()` instead
     */
    public function onEventResizeJs(array $data): bool
    {
        // Check if event resize is enabled
        if (! $this->isEventResizeEnabled()) {
            return false;
        }

        $this->setRawCalendarContextData(Context::EventResize, $data);

        return $this->onEventResize($this->getCalendarContextInfo(), $this->getEventRecord());
    }
}


================================================
FILE: src/Concerns/HandlesNoEventsClick.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\NoEventsClickInfo;

trait HandlesNoEventsClick
{
    protected bool $noEventsClickEnabled = false;

    protected function onNoEventsClick(NoEventsClickInfo $info): void {}

    public function isNoEventsClickEnabled(): bool
    {
        return $this->noEventsClickEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onDateClick` instead
     */
    public function onNoEventsClickJs(array $data): void
    {
        // Check if no events click is enabled
        if (! $this->isNoEventsClickEnabled()) {
            return;
        }

        $this->setRawCalendarContextData(Context::NoEventsClick, $data);

        $this->onNoEventsClick($this->getCalendarContextInfo());
    }
}


================================================
FILE: src/Concerns/HandlesViewDidMount.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\ValueObjects\ViewDidMountInfo;

trait HandlesViewDidMount
{
    protected bool $viewDidMountEnabled = false;

    protected function onViewDidMount(ViewDidMountInfo $info): void {}

    public function isViewDidMountEnabled(): bool
    {
        return $this->viewDidMountEnabled;
    }

    /**
     * @internal Do not override, internal purpose only. Use `onViewDidMount` instead
     */
    public function onViewDidMountJs(array $data): void
    {
        // Check if viewDidMount is enabled
        if (! $this->isViewDidMountEnabled()) {
            return;
        }

        $this->onViewDidMount(new ViewDidMountInfo($data, $this->shouldUseFilamentTimezone()));
    }
}


================================================
FILE: src/Concerns/HasAuthorization.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;

use function Filament\get_authorization_response;

trait HasAuthorization
{
    protected bool $shouldSkipAuthorization = false;

    public function shouldSkipAuthorization(): bool
    {
        return $this->shouldSkipAuthorization;
    }

    public function getAuthorizationResponse(string $action, null | Model | string $recordOrModel = null): Response
    {
        $action = $this->transformActionName($action);

        if ($this->shouldSkipAuthorization()) {
            return Response::allow();
        }

        return get_authorization_response($action, $recordOrModel);
        //        return get_authorization_response($action, $this->getEventRecord(), static::shouldCheckPolicyExistence());
    }

    /**
     * Filament uses 'edit' while laravel uses 'update'.
     * This transforms the action name so laravel's default policy names work.
     */
    protected function transformActionName(string $action): string
    {
        return match ($action) {
            'edit' => 'update',
            default => $action,
        };
    }
}


================================================
FILE: src/Concerns/HasCalendarContextData.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;
use Guava\Calendar\ValueObjects\DateClickInfo;
use Guava\Calendar\ValueObjects\DateSelectInfo;
use Guava\Calendar\ValueObjects\EventClickInfo;
use Guava\Calendar\ValueObjects\EventDropInfo;
use Guava\Calendar\ValueObjects\EventResizeInfo;
use Guava\Calendar\ValueObjects\NoEventsClickInfo;

trait HasCalendarContextData
{
    protected ?array $rawCalendarContextData = null;

    protected function setRawCalendarContextData(Context $context, array $data): void
    {
        $this->rawCalendarContextData = [
            'context' => $context,
            'data' => $data,
            'useFilamentTimezone' => $this->shouldUseFilamentTimezone(),
        ];

        if ($context->interactsWithRecord()) {
            $this->resolveEventRecord();
        }
    }

    public function getRawCalendarContextData(?string $key = null): array | string | null
    {
        if ($key) {
            return data_get($this->rawCalendarContextData, "data.$key");
        }

        if (empty($data = $this->rawCalendarContextData)) {
            return null;
        }

        return $data;
    }

    public function getCalendarContextInfo(): ?ContextualInfo
    {
        $rawContextData = $this->getRawCalendarContextData() ?? $this->getMountedAction()?->getArguments();

        // No contextual data available
        if (! $rawContextData) {
            return null;
        }

        $context = data_get($rawContextData, 'context');
        $data = data_get($rawContextData, 'data');
        $useFilamentTimezone = data_get($rawContextData, 'useFilamentTimezone');

        if (is_string($context)) {
            $context = Context::from($context);
        }

        return match ($context) {
            Context::DateClick => new DateClickInfo($data, $useFilamentTimezone),
            Context::DateSelect => new DateSelectInfo($data, $useFilamentTimezone),
            Context::EventClick => new EventClickInfo($data, $this->getEventRecord(), $useFilamentTimezone),
            Context::NoEventsClick => new NoEventsClickInfo($data, $useFilamentTimezone),
            Context::EventResize => new EventResizeInfo($data, $this->getEventRecord(), $useFilamentTimezone),
            Context::EventDragAndDrop => new EventDropInfo($data, $this->getEventRecord(), $useFilamentTimezone),
        };
    }
}


================================================
FILE: src/Concerns/HasCalendarView.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Enums\CalendarViewType;

trait HasCalendarView
{
    protected CalendarViewType $calendarView = CalendarViewType::DayGridMonth;

    public function getCalendarView(): CalendarViewType
    {
        return $this->calendarView;
    }
}


================================================
FILE: src/Concerns/HasContextMenu.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Actions\Action;
use Guava\Calendar\Enums\Context;
use Illuminate\Support\Collection;
use InvalidArgumentException;

trait HasContextMenu
{
    protected array $cachedContextMenuActions = [];

    public function bootedHasContextMenu(): void
    {
        $this->cacheContextMenuActions();
    }

    public function getContextMenuActionsUsing(Context $context, array $data = []): Collection
    {
        $this->setRawCalendarContextData($context, $data);

        $actions = match ($context) {
            Context::EventClick => $this->getCachedEventClickContextMenuActions(),
            Context::DateClick => $this->getCachedDateClickContextMenuActions(),
            Context::DateSelect => $this->getCachedDateSelectContextMenuActions(),
            Context::NoEventsClick => $this->getCachedNoEventsClickContextMenuActions()
        };

        return collect($actions)
            ->filter(fn (Action $action) => $action->isVisible())
            ->map(
                fn (Action $action) => $action
                    ->arguments($this->getRawCalendarContextData())
                    ->toHtml()
            )
        ;
    }

    public function hasContextMenu(?Context $context = null): bool
    {
        return match ($context) {
            Context::DateClick => ! empty($this->getCachedDateClickContextMenuActions()),
            Context::DateSelect => ! empty($this->getCachedDateSelectContextMenuActions()),
            Context::EventClick => ! empty($this->getCachedEventClickContextMenuActions()),
            Context::NoEventsClick => ! empty($this->getCachedNoEventsClickContextMenuActions()),
            default => ! empty($this->getCachedContextMenuActions()),
        };
    }

    protected function cacheContextMenuActions(): void
    {
        foreach ($this->getDateClickContextMenuActions() as $action) {
            $this->cacheContextMenuAction($action, Context::DateClick);
        }
        foreach ($this->getDateSelectContextMenuActions() as $action) {
            $this->cacheContextMenuAction($action, Context::DateSelect);
        }
        foreach ($this->getEventClickContextMenuActions() as $action) {
            $this->cacheContextMenuAction($action, Context::EventClick);
        }
        foreach ($this->getNoEventsClickContextMenuActions() as $action) {
            $this->cacheContextMenuAction($action, Context::NoEventsClick);
        }
    }

    protected function getCachedContextMenuActions(): array
    {
        return $this->cachedContextMenuActions;
    }

    protected function getDateClickContextMenuActions(): array
    {
        return [];
    }

    protected function getDateSelectContextMenuActions(): array
    {
        return [];
    }

    protected function getEventClickContextMenuActions(): array
    {
        return [];
    }

    protected function getNoEventsClickContextMenuActions(): array
    {
        return [];
    }

    protected function getCachedDateClickContextMenuActions(): array
    {
        return data_get($this->getCachedContextMenuActions(), Context::DateClick->value, []);
    }

    protected function getCachedDateSelectContextMenuActions(): array
    {
        return data_get($this->getCachedContextMenuActions(), Context::DateSelect->value, []);
    }

    protected function getCachedEventClickContextMenuActions(): array
    {
        return data_get($this->getCachedContextMenuActions(), Context::EventClick->value, []);
    }

    protected function getCachedNoEventsClickContextMenuActions(): array
    {
        return data_get($this->getCachedContextMenuActions(), Context::NoEventsClick->value, []);
    }

    private function cacheContextMenuAction(Action $action, Context $context): void
    {
        $action = $action
            ->grouped()
        ;

        if (! $action instanceof Action) {
            throw new InvalidArgumentException('Context menu actions must be an instance of ' . Action::class . '.');
        }

        $this->cacheAction($action);
        $cachedActions = data_get($this->cachedContextMenuActions, $context->value, []);
        $this->cachedContextMenuActions[$context->value] = [
            ...$cachedActions,
            $action,
        ];
    }
}


================================================
FILE: src/Concerns/HasDayMaxEvents.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait HasDayMaxEvents
{
    /**
     * Determines the maximum number of stacked event levels for a given day in the dayGrid view.
     *
     * If there are too many events, a link like +2 more is displayed.
     *
     * Currently, only a boolean value is supported. When set to true, it limits the number of events to the height of the day cell. When set to false (default) there is no limit.
     */
    protected bool $dayMaxEvents = false;

    /**
     * Determines the maximum number of stacked event levels for a given day in the dayGrid view.
     *
     * If there are too many events, a link like +2 more is displayed.
     *
     * Currently, only a boolean value is supported. When set to true, it limits the number of events to the height of the day cell. When set to false (default) there is no limit.
     */
    public function getDayMaxEvents(): bool
    {
        return $this->dayMaxEvents;
    }
}


================================================
FILE: src/Concerns/HasDefaultActions.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Actions\Action;
use Guava\Calendar\Filament\Actions\CreateAction;
use Guava\Calendar\Filament\Actions\DeleteAction;
use Guava\Calendar\Filament\Actions\EditAction;
use Guava\Calendar\Filament\Actions\ViewAction;
use Illuminate\Support\Str;

trait HasDefaultActions
{
    /**
     * Returns a create action configured for the specified model.
     *
     * @param  string  $model  The model class for which you want to make a create action.
     */
    protected function createAction(string $model, ?string $name = null): CreateAction
    {
        if (! $name) {
            $name = Str::of('create')->append(Str::pascal(class_basename($model)))->toString();
        }

        return CreateAction::make($name)
            ->model($model)
        ;
    }

    public function viewAction(): ViewAction
    {
        return ViewAction::make();
    }

    public function editAction(): EditAction
    {
        return EditAction::make();
    }

    public function deleteAction(): DeleteAction
    {
        return DeleteAction::make();
    }
}


================================================
FILE: src/Concerns/HasEventContent.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Attributes\CalendarEventContent;
use Illuminate\Contracts\Support\Htmlable;
use ReflectionClass;

trait HasEventContent
{
    public function getEventContentJs(): ?array
    {
        // Try finding a method with a CalendarEventContent attribute
        $reflectionClass = new ReflectionClass($this);

        $views = [];
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC + \ReflectionMethod::IS_PROTECTED) as $method) {
            $attributes = $method->getAttributes(CalendarEventContent::class);

            foreach ($attributes as $attribute) {
                $content = $this->{$method->getName()}();
                $views[$attribute->newInstance()->model] = $content instanceof Htmlable ? $content->toHtml() : $content;
            }
        }

        // Try finding a "defaultEventContent" or "eventContent" method.
        if (method_exists($this, 'defaultEventContent')) {
            $views['_default'] = $this->defaultEventContent();
        }

        // Try finding a "defaultEventContent" or "eventContent" method.
        if (method_exists($this, 'eventContent')) {
            $views['_default'] = $this->eventContent();
        }

        if (! empty($views)) {
            return $views;
        }

        return null;
    }
}


================================================
FILE: src/Concerns/HasEvents.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Contracts\Eventable;
use Guava\Calendar\ValueObjects\CalendarEvent;
use Guava\Calendar\ValueObjects\FetchInfo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

trait HasEvents
{
    abstract protected function getEvents(FetchInfo $info): Collection | array | Builder;

    public function getEventsJs(array $info): array
    {
        $events = $this->getEvents(new FetchInfo($info));

        if ($events instanceof Builder) {
            $events = $events->get();
        }

        if (is_array($events)) {
            $events = collect($events);
        }

        return $events
            ->map(function (Builder | Collection | Eventable | CalendarEvent $event) use ($info): array {
                if ($event instanceof Eventable) {
                    $event = $event->toCalendarEvent();
                }

                return $event->toCalendarObject(
                    data_get($info, 'tzOffset'),
                    $this->shouldUseFilamentTimezone()
                );
            })
            ->all()
        ;
    }
}


================================================
FILE: src/Concerns/HasFirstDay.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Carbon\WeekDay;

trait HasFirstDay
{
    /**
     * The day that each week begins at, where Sunday is 0, Monday is 1, etc. Saturday is 6.
     */
    protected WeekDay $firstDay = WeekDay::Monday;

    /**
     * The day that each week begins at, where Sunday is 0, Monday is 1, etc. Saturday is 6.
     */
    public function getFirstDay(): WeekDay
    {
        return $this->firstDay;
    }
}


================================================
FILE: src/Concerns/HasFooterActions.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Schema;
use InvalidArgumentException;

trait HasFooterActions
{
    protected array $cachedFooterActions = [];

    public function bootedHasFooterActions(): void
    {
        $this->cacheFooterActions();
    }

    protected function cacheFooterActions(): void
    {
        /** @var Action $action */
        foreach ($this->getFooterActions() as $action) {

            if ($action instanceof ActionGroup) {
                $action->livewire($this);

                /** @var array<string, Action> $flatActions */
                $flatActions = $action->getFlatActions();

                $this->mergeCachedActions($flatActions);
                $this->cachedFooterActions[] = $action;

                continue;
            }

            if (! $action instanceof Action) {
                throw new InvalidArgumentException('Footer actions must be an instance of ' . Action::class . ', or ' . ActionGroup::class . '.');
            }

            $this->cacheAction($action);
            $this->cachedFooterActions[] = $action;
        }
    }

    public function getCachedFooterActions(): array
    {
        return $this->cachedFooterActions;
    }

    public function getFooterActions(): array
    {
        return [];
    }

    public function getCachedFooterActionsComponent(): Actions
    {
        return Actions::make($this->getCachedFooterActions())
            ->container(Schema::make($this))
        ;
    }
}


================================================
FILE: src/Concerns/HasHeaderActions.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Schema;
use InvalidArgumentException;

trait HasHeaderActions
{
    protected array $cachedHeaderActions = [];

    public function bootedHasHeaderActions(): void
    {
        $this->cacheHeaderActions();
    }

    protected function cacheHeaderActions(): void
    {
        /** @var Action $action */
        foreach ($this->getHeaderActions() as $action) {
            if ($action instanceof ActionGroup) {
                $action->livewire($this);

                /** @var array<string, Action> $flatActions */
                $flatActions = $action->getFlatActions();

                $this->mergeCachedActions($flatActions);
                $this->cachedHeaderActions[] = $action;

                continue;
            }

            if (! $action instanceof Action) {
                throw new InvalidArgumentException('Header actions must be an instance of ' . Action::class . ', or ' . ActionGroup::class . '.');
            }

            $this->cacheAction($action);
            $this->cachedHeaderActions[] = $action;
        }
    }

    public function getCachedHeaderActions(): array
    {
        return $this->cachedHeaderActions;
    }

    public function getHeaderActions(): array
    {
        return [];
    }

    public function getCachedHeaderActionsComponent(): Actions
    {
        return Actions::make($this->getCachedHeaderActions())
            ->container(Schema::make($this))
        ;
    }
}


================================================
FILE: src/Concerns/HasHeading.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Illuminate\Support\HtmlString;

trait HasHeading
{
    protected string | HtmlString | null | bool $heading = true;

    public function getHeading(): null | string | HtmlString
    {
        if ($this->heading === false || is_null($this->heading)) {
            return null;
        }

        if ($this->heading === true) {
            return __('guava-calendar::translations.heading');
        }

        return $this->heading;
    }
}


================================================
FILE: src/Concerns/HasLocale.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait HasLocale
{
    /**
     * Defines the locale parameter for the native JavaScript Intl.DateTimeFormat object that EventCalendar uses to format date and time strings in options such as dayHeaderFormat, eventTimeFormat, etc.
     */
    protected ?string $locale = null;

    /**
     * Defines the locale parameter for the native JavaScript Intl.DateTimeFormat object that EventCalendar uses to format date and time strings in options such as dayHeaderFormat, eventTimeFormat, etc.
     */
    public function getLocale(): string
    {
        return $this->locale ?? $this->getDefaultLocale();
    }

    private function getDefaultLocale(): string
    {
        return str(app()->getLocale())
            ->before('_')
            ->toString()
        ;
    }
}


================================================
FILE: src/Concerns/HasMoreLinkContent.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Illuminate\View\View;

trait HasMoreLinkContent
{
    protected ?string $moreLinkContent = null;

    /**
     * vkurko/calendar doesn't support async method calls in moreLinkContent,
     * that's why we need to pass all views to the client side.
     *
     * @return string|array|null null to use default, string to use single view
     */
    public function getMoreLinkContent(): null | string | array
    {
        return $this->moreLinkContent;
    }

    public function getMoreLinkContentJs(): ?string
    {
        return $this->getMoreLinkContent();
    }
}


================================================
FILE: src/Concerns/HasNotifications.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Notifications\Notification;
use Illuminate\Auth\Access\Response;

trait HasNotifications
{
    public function sendUnauthorizedNotification(Response $response): static
    {
        $notification = $this->getUnauthorizedNotification($response);

        if (filled($notification?->getTitle())) {
            $notification->send();
        }

        return $this;
    }

    protected function getUnauthorizedNotification(Response $response): ?Notification
    {
        return Notification::make()
            ->danger()
            ->title($this->getUnauthorizedNotificationTitle($response))
            ->persistent()
        ;
    }

    protected function getUnauthorizedNotificationTitle(Response $response): ?string
    {
        return $response->message();
    }
}


================================================
FILE: src/Concerns/HasOptions.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait HasOptions
{
    protected array $options = [];

    /**
     * Allows you to set vkurko/calendar options directly in case a helper method was not provided by the package.
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    public function setOption(string $key, mixed $value): static
    {
        $this->dispatch('calendar--set', key: $key, value: $value);

        return $this;
    }
}


================================================
FILE: src/Concerns/HasResourceLabelContent.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Attributes\CalendarResourceLabelContent;
use Illuminate\Contracts\Support\Htmlable;
use ReflectionClass;

trait HasResourceLabelContent
{
    public function getResourceLabelContentJs(): ?array
    {
        // Try finding a method with a CalendarEventContent attribute
        $reflectionClass = new ReflectionClass($this);

        $views = [];
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC + \ReflectionMethod::IS_PROTECTED) as $method) {
            $attributes = $method->getAttributes(CalendarResourceLabelContent::class);

            foreach ($attributes as $attribute) {
                $content = $this->{$method->getName()}();
                $views[$attribute->newInstance()->model] = $content instanceof Htmlable ? $content->toHtml() : $content;
            }
        }

        // Try finding a "defaultResourceLabelContent" or "resourceLabelContent" method.
        if (method_exists($this, 'defaultResourceLabelContent')) {
            $views['_default'] = $this->defaultResourceLabelContent();
        }

        // Try finding a "defaultResourceLabelContent" or "resourceLabelContent" method.
        if (method_exists($this, 'resourceLabelContent')) {
            $views['_default'] = $this->resourceLabelContent();
        }

        if (! empty($views)) {
            return $views;
        }

        return null;
    }
}


================================================
FILE: src/Concerns/HasResources.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Guava\Calendar\Contracts\Resourceable;
use Guava\Calendar\ValueObjects\CalendarResource;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

trait HasResources
{
    protected function getResources(): Collection | array | Builder
    {
        return [];
    }

    public function getResourcesJs(): array
    {
        $resources = $this->getResources();

        if ($resources instanceof Builder) {
            $resources = $resources->get();
        }

        if (is_array($resources)) {
            $resources = collect($resources);
        }

        return $resources
            ->map(static function (Resourceable | CalendarResource $resource): array {
                if ($resource instanceof Resourceable) {
                    $resource = $resource->toCalendarResource();
                }

                return $resource->toCalendarObject();
            })
            ->toArray()
        ;
    }
}


================================================
FILE: src/Concerns/HasSchema.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Facades\Filament;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Guava\Calendar\Attributes\CalendarSchema;
use Guava\Calendar\Exceptions\SchemaNotFoundException;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionMethod;

trait HasSchema
{
    /**
     * @throws SchemaNotFoundException
     */
    public function getSchemaForModel(Schema $schema, ?string $model = null): Schema
    {
        // Try finding a method with a ForModel attribute
        $reflectionClass = new ReflectionClass($this);

        foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED) as $method) {
            $attributes = $method->getAttributes(CalendarSchema::class);

            foreach ($attributes as $attribute) {
                if ($model === $attribute->newInstance()->model) {
                    return $this->{$method->getName()}($schema);
                }
            }
        }

        // Try guessing and finding a method with the correct name (<camelCaseModel>Schema, such as fooBarSchema)
        $methodName = Str::of(class_basename($model))
            ->camel()
            ->append('Schema')
            ->toString()
        ;
        if (method_exists($this, $methodName)) {
            return $this->{$methodName}($schema);
        }

        // Try finding a "defaultSchema" or "schema" method.
        if (method_exists($this, 'defaultSchema')) {
            return $this->defaultSchema($schema);
        }

        if (method_exists($this, 'schema')) {
            return $this->schema($schema);
        }

        throw new SchemaNotFoundException;
    }

    /**
     * @throws SchemaNotFoundException
     */
    public function getFormSchemaForModel(Schema $schema, ?string $model = null): Schema
    {
        try {
            return $this->getSchemaForModel($schema, $model);
        } catch (SchemaNotFoundException $e) {
            // Try to find form schema in resource
            /** @var resource $resource */
            if ($resource = Filament::getModelResource($model)) {
                return $resource::form($schema);
            }

            throw $e;
        }
    }

    /**
     * @throws SchemaNotFoundException
     */
    public function getInfolistSchemaForModel(Schema $schema, ?string $model = null): Schema
    {
        try {
            return $this->getSchemaForModel($schema, $model);
        } catch (SchemaNotFoundException $e) {
            // Try to find infolist schema in resource
            /** @var resource $resource */
            if ($resource = Filament::getModelResource($model)) {
                return $resource::infolist($schema);
            }

            throw $e;
        }
    }
}


================================================
FILE: src/Concerns/HasTheme.php
================================================
<?php

namespace Guava\Calendar\Concerns;

trait HasTheme
{
    public function getTheme(): ?array
    {
        return [];
    }
}


================================================
FILE: src/Concerns/InteractsWithCalendar.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Schemas\Concerns\InteractsWithSchemas;
use Filament\Support\Concerns\EvaluatesClosures;

trait InteractsWithCalendar
{
    use CanRefreshCalendar;
    use CanUseFilamentTimezone;
    use EvaluatesClosures;
    use HandlesDateClick;
    use HandlesDateSelect;
    use HandlesDatesSet;
    use HandlesEventAllUpdated;
    use HandlesEventClick;
    use HandlesEventDragAndDrop;
    use HandlesEventResize;
    use HandlesNoEventsClick;
    use HandlesViewDidMount;
    use HasCalendarContextData;
    use HasCalendarView;
    use HasContextMenu;
    use HasDayMaxEvents;
    use HasDefaultActions;
    use HasEventContent;
    use HasEvents;
    use HasFirstDay;
    use HasFooterActions;
    use HasHeaderActions;
    use HasHeading;
    use HasLocale;
    use HasMoreLinkContent;
    use HasOptions;
    use HasResourceLabelContent;
    use HasResources;
    use HasSchema;
    use HasTheme;
    use InteractsWithActions {
        InteractsWithActions::mountAction as baseMountAction;
    }
    use InteractsWithEventRecord;
    use InteractsWithSchemas;

    public function mountAction(string $name, array $arguments = [], array $context = []): mixed
    {
        return $this->baseMountAction($name, [
            ...$arguments,
            ...$this->getRawCalendarContextData() ?? [],
        ], $context);
    }
}


================================================
FILE: src/Concerns/InteractsWithEventRecord.php
================================================
<?php

namespace Guava\Calendar\Concerns;

use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Livewire\Attributes\Locked;

trait InteractsWithEventRecord
{
    #[Locked]
    public ?Model $eventRecord = null;

    public function getEventRecord(): ?Model
    {
        return $this->eventRecord;
    }

    public function getEventModel(): ?string
    {
        if ($record = $this->getEventRecord()) {
            return $record::class;
        }

        return null;
    }

    protected function resolveEventRecord(): ?Model
    {
        $model = $this->getRawCalendarContextData('event.extendedProps.model');
        $key = $this->getRawCalendarContextData('event.extendedProps.key');

        // Cannot resolve event record
        if (! $model || ! $key) {
            throw new Exception('Could not resolve event record. A [model] or [key] property set in the [extendedProps] of the mounted event was missing.');
        }

        if ($record = $this->resolveEventRecordRouteBinding($model, $key)) {
            return $this->eventRecord = $record;
        }

        throw (new ModelNotFoundException)->setModel($model, [$key]);
    }

    protected function resolveEventRecordRouteBinding(string $model, mixed $key): ?Model
    {
        return app($model)
            ->resolveRouteBindingQuery($this->getEloquentQuery($model), $key, $this->getEventRecordRouteKeyName($model))
            ->first()
        ;
    }

    protected function getEloquentQuery(string $model): Builder
    {
        return app($model)::query();
    }

    protected function getEventRecordRouteKeyName(?string $model = null): ?string
    {
        return null;
    }
}


================================================
FILE: src/Contracts/ContextualInfo.php
================================================
<?php

namespace Guava\Calendar\Contracts;

use Guava\Calendar\Enums\Context;

interface ContextualInfo
{
    public function getContext(): Context;
}


================================================
FILE: src/Contracts/Eventable.php
================================================
<?php

namespace Guava\Calendar\Contracts;

use Guava\Calendar\ValueObjects\CalendarEvent;

interface Eventable
{
    public function toCalendarEvent(): CalendarEvent;
}


================================================
FILE: src/Contracts/HasCalendar.php
================================================
<?php

namespace Guava\Calendar\Contracts;

interface HasCalendar {}


================================================
FILE: src/Contracts/Resourceable.php
================================================
<?php

namespace Guava\Calendar\Contracts;

use Guava\Calendar\ValueObjects\CalendarResource;

interface Resourceable
{
    public function toCalendarResource(): CalendarResource;
}


================================================
FILE: src/Enums/CalendarViewType.php
================================================
<?php

namespace Guava\Calendar\Enums;

enum CalendarViewType: string
{
    case DayGridMonth = 'dayGridMonth';
    case ListDay = 'listDay';
    case ListWeek = 'listWeek';
    case ListMonth = 'listMonth';
    case ListYear = 'listYear';
    case ResourceTimeGridDay = 'resourceTimeGridDay';
    case ResourceTimeGridWeek = 'resourceTimeGridWeek';
    case ResourceTimelineDay = 'resourceTimelineDay';
    case ResourceTimelineWeek = 'resourceTimelineWeek';
    case ResourceTimelineMonth = 'resourceTimelineMonth';
    case TimeGridDay = 'timeGridDay';
    case TimeGridWeek = 'timeGridWeek';

}


================================================
FILE: src/Enums/Context.php
================================================
<?php

namespace Guava\Calendar\Enums;

enum Context: string
{
    case DateClick = 'dateClick';
    case DateSelect = 'dateSelect';
    case EventClick = 'eventClick';
    case NoEventsClick = 'noEventsClick';
    case EventResize = 'eventResize';
    case EventDragAndDrop = 'eventDragAndDrop';

    public function interactsWithRecord(): bool
    {
        return match ($this) {
            self::EventClick, self::EventResize, self::EventDragAndDrop => true,
            default => false
        };
    }
}


================================================
FILE: src/Exceptions/SchemaNotFoundException.php
================================================
<?php

namespace Guava\Calendar\Exceptions;

use Exception;

class SchemaNotFoundException extends Exception {}


================================================
FILE: src/Filament/Actions/CreateAction.php
================================================
<?php

namespace Guava\Calendar\Filament\Actions;

use Filament\Schemas\Schema;
use Guava\Calendar\Concerns\CalendarAction;
use Guava\Calendar\Contracts\HasCalendar;

class CreateAction extends \Filament\Actions\CreateAction
{
    use CalendarAction;

    protected function setUp(): void
    {
        parent::setUp();

        $this
            ->schema(
                fn (Schema $schema, CreateAction $action, HasCalendar $livewire) => $livewire
                    ->getFormSchemaForModel($schema, $action->getModel())
            )
            ->after(fn (HasCalendar $livewire) => $livewire->refreshRecords())
            ->cancelParentActions()
        ;
    }
}


================================================
FILE: src/Filament/Actions/DeleteAction.php
================================================
<?php

namespace Guava\Calendar\Filament\Actions;

use Guava\Calendar\Concerns\CalendarAction;
use Guava\Calendar\Contracts\HasCalendar;
use Illuminate\Database\Eloquent\Model;

class DeleteAction extends \Filament\Actions\DeleteAction
{
    use CalendarAction;

    protected function setUp(): void
    {
        parent::setUp();

        $this->model(fn (HasCalendar $livewire) => $livewire->getEventModel());
        $this->record(fn (HasCalendar $livewire) => $livewire->getEventRecord());

        $this->after(function (HasCalendar $livewire) {
            $livewire->eventRecord = null;
            $livewire->refreshRecords();
        });

        $this->hidden(static function (?Model $record): bool {
            if (! $record) {
                return false;
            }

            if (! method_exists($record, 'trashed')) {
                return false;
            }

            return $record->trashed();
        });

        $this->cancelParentActions();
    }
}


================================================
FILE: src/Filament/Actions/EditAction.php
================================================
<?php

namespace Guava\Calendar\Filament\Actions;

use Filament\Schemas\Schema;
use Guava\Calendar\Contracts\HasCalendar;

class EditAction extends \Filament\Actions\EditAction
{
    protected function setUp(): void
    {
        parent::setUp();

        $this
            ->model(fn (HasCalendar $livewire) => $livewire->getEventModel())
            ->record(fn (HasCalendar $livewire) => $livewire->getEventRecord())
            ->schema(
                fn (EditAction $action, Schema $schema, HasCalendar $livewire): Schema => $livewire
                    ->getFormSchemaForModel($schema, $action->getModel())
                    ->record($livewire->getEventRecord())
            )->after(fn (HasCalendar $livewire) => $livewire->refreshRecords())
            ->cancelParentActions()
        ;
    }
}


================================================
FILE: src/Filament/Actions/ViewAction.php
================================================
<?php

namespace Guava\Calendar\Filament\Actions;

use Filament\Schemas\Schema;
use Guava\Calendar\Contracts\HasCalendar;

class ViewAction extends \Filament\Actions\ViewAction
{
    protected function setUp(): void
    {
        parent::setUp();

        $this
            ->model(fn (HasCalendar $livewire) => $livewire->getEventModel())
            ->record(fn (HasCalendar $livewire) => $livewire->getEventRecord())
            ->schema(
                fn (ViewAction $action, Schema $schema, HasCalendar $livewire) => $livewire
                    ->getInfolistSchemaForModel($schema, $action->getModel())
                    ->record($livewire->getEventRecord()),
            )
            ->cancelParentActions()
        ;
    }
}


================================================
FILE: src/Filament/CalendarWidget.php
================================================
<?php

namespace Guava\Calendar\Filament;

use Filament\Actions\Contracts\HasActions;
use Filament\Schemas\Contracts\HasSchemas;
use Filament\Widgets\Widget;
use Guava\Calendar\Concerns\InteractsWithCalendar;
use Guava\Calendar\Contracts\HasCalendar;

abstract class CalendarWidget extends Widget implements HasActions, HasCalendar, HasSchemas
{
    use InteractsWithCalendar;

    protected string $view = 'guava-calendar::widgets.calendar-widget';

    protected int | string | array $columnSpan = 'full';
}


================================================
FILE: src/ValueObjects/CalendarEvent.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\Carbon;
use Filament\Support\Facades\FilamentTimezone;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;

use function Guava\Calendar\utc_to_user_local_time;

class CalendarEvent
{
    protected string | Htmlable $title;

    protected Carbon $start;

    protected Carbon $end;

    protected bool $allDay = false;

    protected ?string $backgroundColor = null;

    protected ?string $textColor = null;

    protected ?string $display = null;

    protected ?bool $editable = null;

    protected ?bool $startEditable = null;

    protected ?bool $durationEditable = null;

    protected array $resourceIds = [];

    protected array $extendedProps = [];

    protected array $styles = [];

    protected array $classNames = [];

    protected ?string $timezone = null;

    private function __construct(?Model $model = null)
    {
        if ($model) {
            $this->key($model->getKey());
            $this->model($model::class);
        }
    }

    public function start(string | Carbon $start): static
    {
        $start = is_string($start)
            ? Carbon::make($start)
            : $start;

        $this->start = $start; // ->setTimezone(FilamentTimezone::get());
        //        $this->start = $start->setTimezone(FilamentTimezone::get());

        return $this;
    }

    public function getStart(): Carbon
    {
        return $this->start;
    }

    public function end(string | Carbon $end): static
    {
        $end = is_string($end)
            ? Carbon::make($end)
            : $end;

        $this->end = $end; // ->setTimezone(FilamentTimezone::get());
        //        $this->end = $end->setTimezone(FilamentTimezone::get());

        return $this;
    }

    public function getEnd(): Carbon
    {
        return $this->end;
    }

    public function allDay(bool $allDay = true): static
    {
        $this->allDay = $allDay;

        return $this;
    }

    public function getAllDay(): bool
    {
        return $this->allDay;
    }

    public function title(string | Htmlable $title): static
    {
        $this->title = $title;

        return $this;
    }

    public function getTitle(): string | Htmlable
    {
        return $this->title;
    }

    // TODO: Support arrays (such as Color::Rose from Filament) and shade selection (default 400 or 600)
    // TODO: also support filament color names, such as 'primary' or 'danger'
    public function backgroundColor(string $color): static
    {
        $this->backgroundColor = $color;

        return $this;
    }

    public function getBackgroundColor(): ?string
    {
        return $this->backgroundColor;
    }

    public function textColor(string $color): static
    {
        $this->textColor = $color;

        return $this;
    }

    public function getTextColor(): ?string
    {
        return $this->textColor;
    }

    public function classes(array $classes): static
    {
        return $this->classNames($classes);
    }

    public function classNames(array $classNames): static
    {
        $this->classNames = $classNames;

        return $this;
    }

    public function getClassNames(): string
    {
        return Arr::toCssClasses($this->classNames);
    }

    public function styles(array $styles): static
    {
        $this->styles = $styles;

        return $this;
    }

    public function getStyles(): string
    {
        $styles = [];

        foreach ($this->styles as $key => $value) {
            if (is_int($key)) {
                $styles[] = Str::finish($value, ';');
            } elseif (! is_bool($value)) {
                $styles[] = Str::finish("$key: $value", ';');
            } elseif ($value) {
                $styles[] = Str::finish($key, ';');
            }
        }

        return implode(' ', $styles);
    }

    public function display(string $display): static
    {
        $this->display = $display;

        return $this;
    }

    public function getDisplay(): ?string
    {
        return $this->display;
    }

    public function displayAuto(): static
    {
        return $this->display('auto');
    }

    public function displayBackground(): static
    {
        return $this->display('background');
    }

    public function editable(bool $editable = true): static
    {
        $this->editable = $editable;

        return $this;
    }

    public function getEditable(): ?bool
    {
        return $this->editable;
    }

    public function startEditable(bool $startEditable = true): static
    {
        $this->startEditable = $startEditable;

        return $this;
    }

    public function getStartEditable(): ?bool
    {
        return $this->startEditable;
    }

    public function durationEditable(bool $durationEditable = true): static
    {
        $this->durationEditable = $durationEditable;

        return $this;
    }

    public function getDurationEditable(): ?bool
    {
        return $this->durationEditable;
    }

    public function resourceId(int | string | CalendarResource $resource): static
    {
        $this->resourceIds([$resource]);

        return $this;
    }

    public function resourceIds(array $resourceIds): static
    {
        $this->resourceIds = [
            ...$this->resourceIds,
            ...$resourceIds,
        ];

        return $this;
    }

    public function getResourceIds(): array
    {
        return $this->resourceIds;
    }

    public function url(string $url, string $target = '_blank'): static
    {
        $this->extendedProp('url', $url);
        $this->extendedProp('url_target', $target);

        return $this;
    }

    public function key(string $key): static
    {
        $this->extendedProp('key', $key);

        return $this;
    }

    public function model(string $model): static
    {
        $this->extendedProp('model', $model);

        return $this;
    }

    public function action(string $action): static
    {
        $this->extendedProp('action', $action);

        return $this;
    }

    public function extendedProp(string $key, mixed $value): static
    {
        data_set($this->extendedProps, $key, $value);

        return $this;
    }

    public function extendedProps(array $props): static
    {
        $this->extendedProps = [
            ...$this->extendedProps,
            ...$props,
        ];

        return $this;
    }

    public function timezone(string $timezone): static
    {
        $this->timezone = $timezone;

        return $this;
    }

    public function getExtendedProps(): array
    {
        return $this->extendedProps;
    }

    public static function make(?Model $model = null): static
    {
        return new static($model);
    }

    public function toCalendarObject(int $timezoneOffset, bool $useFilamentTimezone): array
    {
        $array = [
            'title' => $this->getTitle() instanceof Htmlable ? ['html' => $this->getTitle()->toHtml()] : $this->getTitle(),
            'start' => $useFilamentTimezone
                ? $this->getStart()->setTimezone($this->timezone ?? FilamentTimezone::get())->toIso8601String()
                : $this->getStart()->utcOffset($timezoneOffset)->toIso8601String(),
            'end' => $useFilamentTimezone
                ? $this->getEnd()->setTimezone($this->timezone ?? FilamentTimezone::get())->toIso8601String()
                : $this->getEnd()->utcOffset($timezoneOffset)->toIso8601String(),
            'allDay' => $this->getAllDay(),
            'backgroundColor' => $this->getBackgroundColor(),
            'textColor' => $this->getTextColor(),
            'styles' => $this->getStyles(),
            'classNames' => $this->getClassNames(),
            'resourceIds' => $this->getResourceIds(),
            'extendedProps' => $this->getExtendedProps(),
        ];

        if (($editable = $this->getEditable()) !== null) {
            $array['editable'] = $editable;
        }

        if (($startEditable = $this->getStartEditable()) !== null) {
            $array['startEditable'] = $startEditable;
        }

        if (($durationEditable = $this->getDurationEditable()) !== null) {
            $array['durationEditable'] = $durationEditable;
        }

        if (($display = $this->getDisplay()) !== null) {
            $array['display'] = $display;
        }

        return $array;
    }

    public function fromCalendarObject(array $data, int $timezoneOffset, bool $useFilamentTimezone): static
    {
        $title = $data['title'];
        if (is_array($data['title']) && array_key_exists('html', $data['title'])) {
            $title = new HtmlString($data['title']['html']);
        }
        $this
            ->title($title)
            ->start(utc_to_user_local_time($data['start'], $timezoneOffset, $useFilamentTimezone))
            ->end(utc_to_user_local_time($data['end'], $timezoneOffset, $useFilamentTimezone))
            ->allDay($data['allDay'])
            ->styles($data['styles'])
            ->classNames($data['classNames'])
            ->extendedProps($data['extendedProps'])
            ->display($data['display'])
            ->resourceIds($data['resourceIds'])
        ;

        if ($backgroundColor = data_get($data, 'backgroundColor')) {
            $this->backgroundColor($backgroundColor);
        }

        return $this;
    }
}


================================================
FILE: src/ValueObjects/CalendarResource.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;

class CalendarResource
{
    protected int | string $id;

    protected string | Htmlable $title;

    protected ?string $eventBackgroundColor = null;

    protected ?string $eventTextColor = null;

    protected array $children = [];

    protected array $extendedProps = [];

    private function __construct(Model | int | string $id)
    {
        if ($id instanceof Model) {
            $this->id = $id->getKey();
        } else {
            $this->id = $id;
        }
    }

    public function getId(): int | string
    {
        return $this->id;
    }

    public function title(string | Htmlable $title): static
    {
        $this->title = $title;

        return $this;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    // TODO: Support arrays (such as Color::Rose from Filament) and shade selection (default 400 or 600)
    // TODO: also support filament color names, such as 'primary' or 'danger'
    public function eventBackgroundColor(string $color): static
    {
        $this->eventBackgroundColor = $color;

        return $this;
    }

    public function getEventBackgroundColor(): ?string
    {
        return $this->eventBackgroundColor;
    }

    public function eventTextColor(string $color): static
    {
        $this->eventTextColor = $color;

        return $this;
    }

    public function getEventTextColor(): ?string
    {
        return $this->eventTextColor;
    }

    public function child(array | CalendarResource $child): static
    {
        return $this->children([$child]);
    }

    public function children(array $children): static
    {
        $this->children = [
            ...$this->children,
            ...$children,
        ];

        return $this;
    }

    public function getChildren(): array
    {
        return $this->children;
    }

    public function extendedProp(string $key, mixed $value): static
    {
        data_set($this->extendedProps, $key, $value);

        return $this;
    }

    public function extendedProps(array $props): static
    {
        $this->extendedProps = [
            ...$this->extendedProps,
            ...$props,
        ];

        return $this;
    }

    public function getExtendedProps(): array
    {
        return $this->extendedProps;
    }

    public static function make(Model | int | string $id): static
    {
        return new static($id);
    }

    public function toCalendarObject(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->getTitle() instanceof Htmlable ? ['html' => $this->getTitle()->toHtml()] : $this->getTitle(),
            'eventBackgroundColor' => $this->getEventBackgroundColor(),
            'eventTextColor' => $this->getEventTextColor(),
            'children' => collect($this->getChildren())->toArray(),
            'extendedProps' => $this->getExtendedProps(),
        ];
    }

    public static function fromCalendarObject(array $data): CalendarResource
    {
        $resource = CalendarResource::make(data_get($data, 'id'));

        $resource->title(data_get($data, 'title'));

        if ($eventBackgroundColor = data_get($data, 'eventBackgroundColor')) {
            $resource->eventBackgroundColor($eventBackgroundColor);
        }

        if ($eventTextColor = data_get($data, 'eventTextColor')) {
            $resource->eventTextColor($eventTextColor);
        }

        if ($extendedProps = data_get($data, 'extendedProps')) {
            $resource->extendedProps($extendedProps);
        }

        return $resource;
    }
}


================================================
FILE: src/ValueObjects/CalendarView.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\CarbonImmutable;
use Guava\Calendar\Enums\CalendarViewType;

use function Guava\Calendar\utc_to_user_local_time;

readonly class CalendarView
{
    public CalendarViewType $type;

    public string $title;

    public CalendarView $view;

    public CarbonImmutable $currentStart;

    public CarbonImmutable $currentEnd;

    public CarbonImmutable $activeStart;

    public CarbonImmutable $activeEnd;

    protected array $originalData;

    public function __construct(array $data, int $timezoneOffset, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->type = CalendarViewType::tryFrom(data_get($data, 'type'));
        $this->title = data_get($data, 'title');
        $this->currentStart = utc_to_user_local_time(data_get($data, 'currentStart'), $timezoneOffset, $useFilamentTimezone);
        $this->currentEnd = utc_to_user_local_time(data_get($data, 'currentEnd'), $timezoneOffset, $useFilamentTimezone);
        $this->activeStart = utc_to_user_local_time(data_get($data, 'activeStart'), $timezoneOffset, $useFilamentTimezone);
        $this->activeEnd = utc_to_user_local_time(data_get($data, 'activeEnd'), $timezoneOffset, $useFilamentTimezone);
    }
}


================================================
FILE: src/ValueObjects/DateClickInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\CarbonImmutable;
use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;

use function Guava\Calendar\utc_to_user_local_time;

readonly class DateClickInfo implements ContextualInfo
{
    public CarbonImmutable $date;

    public bool $allDay;

    public CalendarView $view;

    public ?CalendarResource $resource;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->date = utc_to_user_local_time(
            data_get($data, 'date'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        $this->allDay = data_get($data, 'allDay');

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        if ($resource = data_get($data, 'resource')) {
            $this->resource = CalendarResource::fromCalendarObject($resource);
        } else {
            $this->resource = null;
        }
    }

    public function getContext(): Context
    {
        return Context::DateClick;
    }
}


================================================
FILE: src/ValueObjects/DateSelectInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\CarbonImmutable;
use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;

use function Guava\Calendar\utc_to_user_local_time;

readonly class DateSelectInfo implements ContextualInfo
{
    public CarbonImmutable $start;

    public CarbonImmutable $end;

    public bool $allDay;

    public CalendarView $view;

    public ?CalendarResource $resource;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->start = utc_to_user_local_time(
            data_get($data, 'start'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        $this->end = utc_to_user_local_time(
            data_get($data, 'end'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        $this->allDay = data_get($data, 'allDay');

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        if ($resource = data_get($data, 'resource')) {
            $this->resource = CalendarResource::fromCalendarObject($resource);
        } else {
            $this->resource = null;
        }
    }

    public function getContext(): Context
    {
        return Context::DateSelect;
    }
}


================================================
FILE: src/ValueObjects/DatesSetInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\CarbonImmutable;

use function Guava\Calendar\utc_to_user_local_time;

readonly class DatesSetInfo
{
    public CarbonImmutable $start;

    public CarbonImmutable $end;

    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->start = utc_to_user_local_time(
            data_get($data, 'start'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        $this->end = utc_to_user_local_time(
            data_get($data, 'end'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }
}


================================================
FILE: src/ValueObjects/EventAllUpdatedInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

readonly class EventAllUpdatedInfo
{
    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }
}


================================================
FILE: src/ValueObjects/EventClickInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;
use Illuminate\Database\Eloquent\Model;

readonly class EventClickInfo implements ContextualInfo
{
    public Model $record;

    public CalendarEvent $event;

    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, Model $record, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->record = $record;

        $this->event = CalendarEvent::make($record)
            ->fromCalendarObject(
                data_get($data, 'event'),
                data_get($data, 'tzOffset'),
                $useFilamentTimezone
            )
        ;

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }

    public function getContext(): Context
    {
        return Context::EventClick;
    }
}


================================================
FILE: src/ValueObjects/EventDropInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;
use Illuminate\Database\Eloquent\Model;

readonly class EventDropInfo implements ContextualInfo
{
    public Model $record;

    public CalendarEvent $event;

    public CalendarEvent $oldEvent;

    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, Model $record, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->record = $record;

        $this->event = CalendarEvent::make($record)
            ->fromCalendarObject(
                data_get($data, 'event'),
                data_get($data, 'tzOffset'),
                $useFilamentTimezone
            )
        ;

        $this->oldEvent = CalendarEvent::make($record)
            ->fromCalendarObject(
                data_get($data, 'oldEvent'),
                data_get($data, 'tzOffset'),
                $useFilamentTimezone
            )
        ;

        // todo: resource and old resource
        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }

    public function getContext(): Context
    {
        return Context::EventDragAndDrop;
    }
}


================================================
FILE: src/ValueObjects/EventResizeInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;
use Illuminate\Database\Eloquent\Model;

readonly class EventResizeInfo implements ContextualInfo
{
    public Model $record;

    public CalendarEvent $event;

    public CalendarEvent $oldEvent;

    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, Model $record, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->record = $record;

        $this->event = CalendarEvent::make($record)
            ->fromCalendarObject(
                data_get($data, 'event'),
                data_get($data, 'tzOffset'),
                $useFilamentTimezone
            )
        ;

        $this->oldEvent = CalendarEvent::make($record)
            ->fromCalendarObject(
                data_get($data, 'oldEvent'),
                data_get($data, 'tzOffset'),
                $useFilamentTimezone
            )
        ;

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }

    public function getContext(): Context
    {
        return Context::EventResize;
    }
}


================================================
FILE: src/ValueObjects/FetchInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Carbon\CarbonImmutable;

use function Guava\Calendar\browser_date_to_app_date;

readonly class FetchInfo
{
    public CarbonImmutable $start;

    public CarbonImmutable $end;

    protected array $originalData;

    public function __construct(array $data)
    {
        $this->originalData = $data;

        $this->start = browser_date_to_app_date(CarbonImmutable::make($data['startStr']));
        $this->end = browser_date_to_app_date(CarbonImmutable::make($data['endStr']));
    }
}


================================================
FILE: src/ValueObjects/NoEventsClickInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

use Guava\Calendar\Contracts\ContextualInfo;
use Guava\Calendar\Enums\Context;

readonly class NoEventsClickInfo implements ContextualInfo
{
    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }

    public function getContext(): Context
    {
        return Context::NoEventsClick;
    }
}


================================================
FILE: src/ValueObjects/ViewDidMountInfo.php
================================================
<?php

namespace Guava\Calendar\ValueObjects;

readonly class ViewDidMountInfo
{
    public CalendarView $view;

    protected array $originalData;

    public function __construct(array $data, bool $useFilamentTimezone)
    {
        $this->originalData = $data;

        $this->view = new CalendarView(
            data_get($data, 'view'),
            data_get($data, 'tzOffset'),
            $useFilamentTimezone
        );
    }
}


================================================
FILE: src/helpers.php
================================================
<?php

namespace Guava\Calendar;

use Carbon\CarbonImmutable;
use Filament\Support\Facades\FilamentTimezone;

if (! function_exists('Guava\Calendar\browser_date_to_user_date')) {
    /**
     * The underlying EventCalendar does not support timezones and thus all times in the calendar
     * are provided in either UTC or their local browser locale.
     *
     * To work out of the box in any filament installation and support filament v4 timezone settings,
     * this method converts the users local browser date into the filament timezone and then converts
     * it into the apps timezone.
     */
    function browser_date_to_user_date(CarbonImmutable | string $date): CarbonImmutable
    {
        if (is_string($date)) {
            $date = CarbonImmutable::make($date);
        }

        return $date->shiftTimezone(FilamentTimezone::get());
    }
}

if (! function_exists('Guava\Calendar\browser_date_to_app_date')) {
    /**
     * The underlying EventCalendar does not support timezones and thus all times in the calendar
     * are provided in either UTC or their local browser locale.
     *
     * To work out of the box in any filament installation and support filament v4 timezone settings,
     * this method converts the users local browser date into the filament timezone and then converts
     * it into the apps timezone.
     */
    function browser_date_to_app_date(CarbonImmutable | string $date): CarbonImmutable
    {
        if (is_string($date)) {
            $date = CarbonImmutable::make($date);
        }

        return browser_date_to_user_date($date)->setTimezone(config('app.timezone'));
    }
}

if (! function_exists('Guava\Calendar\utc_to_user_local_time')) {
    /**
     * The underlying EventCalendar does not support timezones and thus all times in the calendar
     * are provided in either UTC or their local browser locale.
     *
     * To work out of the box in any filament installation and support filament v4 timezone settings,
     * this method converts the users local browser date into the filament timezone and then converts
     * it into the apps timezone.
     */
    function utc_to_user_local_time(CarbonImmutable | string $date, int $timezoneOffset, bool $useFilamentTimezone = false): CarbonImmutable
    {
        if (is_string($date)) {
            $date = CarbonImmutable::make($date);
        }

        // This converts the UTC time to the user's local time, by offsetting the timezone
        // This will result in a time in the user's local browser time
        $date = $date->utcOffset($timezoneOffset);

        // This will shift the timezone to the timezone set by filament,
        // essentially treating the user's local browser timezone as the one set by filament
        // This basically overrides the timezone and "adds support" for setting the timezone
        // despite EventCalendar not supporting timezones.
        if ($useFilamentTimezone) {
            $date = $date->shiftTimezone(FilamentTimezone::get());
        }

        return $date;
    }
}


================================================
FILE: tests/ArchTest.php
================================================
<?php

it('will not use debugging functions')
    ->expect(['dd', 'dump', 'ray'])
    ->each->not->toBeUsed()
;


================================================
FILE: tests/Pest.php
================================================
<?php

/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/

// uses(Tests\TestCase::class)->in('Feature');

/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/

expect()->extend('toBeOne', function () {
    return $this->toBe(1);
});

/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/

function something()
{
    // ..
}


================================================
FILE: tests/TestCase.php
================================================
<?php

namespace Guava\Calendar\Tests;

use Guava\Calendar\CalendarServiceProvider;
use Illuminate\Database\Eloquent\Factories\Factory;
use PHPUnit\Framework\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        Factory::guessFactoryNamesUsing(
            fn (string $modelName) => 'Guava\\Calendar\\Database\\Factories\\'.class_basename($modelName).'Factory'
        );
    }

    protected function getPackageProviders($app)
    {
        return [
            CalendarServiceProvider::class,
        ];
    }

    public function getEnvironmentSetUp($app)
    {
        config()->set('database.default', 'testing');

        /*
        $migration = include __DIR__.'/../database/migrations/create_calendar_table.php.stub';
        $migration->up();
        */
    }
}


================================================
FILE: tests/Unit/ValueObjects/EventTest.php
================================================
<?php

use Carbon\Carbon;
use Guava\Calendar\ValueObjects\CalendarEvent;
use Illuminate\Support\HtmlString;

beforeEach(function () {
    $this->event = CalendarEvent::make();
});

it('should set start and end', function () {
    $start = Carbon::now();
    $end = Carbon::now()->addHour();

    $this->event->start($start);
    $this->event->end($end);

    expect($this->event->getStart())->toBe($start);
    expect($this->event->getEnd())->toBe($end);
});

it('should set all day', function () {
    $this->event->allDay(true);

    expect($this->event->getAllDay())->toBeTrue();
});

it('should set the title', function () {
    $title = 'Test Event';
    $this->event->title($title);

    expect($this->event->getTitle())->toBe($title);
});

it('should set the html title', function () {
    $title = new HtmlString('<strong>Test Event</strong>');
    $this->event->title($title);

    expect($this->event->getTitle())->toBe($title);
});

it('should set the background color', function () {
    $color = '#ff0000';
    $this->event->backgroundColor($color);

    expect($this->event->getBackgroundColor())->toBe($color);
});

it('should set the text color', function () {
    $color = '#00ff00';
    $this->event->textColor($color);

    expect($this->event->getTextColor())->toBe($color);
});

it('should set the styles', function () {
    $styles = [
        'color: red',
        'display: flex; flex-direction: row;',
        'font-size' => '12px',
        'opacity' => 1,
        'background-color: blue' => false,
        'border-color: red' => true,
    ];
    $this->event->styles($styles);

    expect($this->event->getStyles())->toBe('color: red; display: flex; flex-direction: row; font-size: 12px; opacity: 1; border-color: red;');
});

it('should set some classes', function () {
    $classes = ['class-1', 'class-2' => true, 'class3' => false];
    $this->event->classNames($classes);

    expect($this->event->getClassNames())->toBe('class-1 class-2');
});

it('should set the display', function () {
    $display = 'block';
    $this->event->display($display);

    expect($this->event->getDisplay())->toBe($display);
});

it('should set the editable features', function () {
    $this->event->editable(true);
    expect($this->event->getEditable())->toBeTrue();

    $this->event->startEditable(true);
    expect($this->event->getStartEditable())->toBeTrue();

    $this->event->durationEditable(true);
    expect($this->event->getDurationEditable())->toBeTrue();
});

it('should set resource ids', function () {
    $resourceIds = [1, 2, 3];
    $this->event->resourceIds($resourceIds);

    expect($this->event->getResourceIds())->toBe($resourceIds);
});

it('should set some extended props', function ()
Download .txt
gitextract_73e1rlqs/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── SECURITY.md
│   └── workflows/
│       ├── phpstan.yml
│       ├── release.yml
│       └── run-tests.yml
├── .gitignore
├── .gitmodules
├── .releaserc.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── bin/
│   └── build.js
├── composer.json
├── config/
│   └── calendar.php
├── database/
│   ├── factories/
│   │   └── ModelFactory.php
│   └── migrations/
│       └── create_calendar_table.php.stub
├── dist/
│   └── js/
│       ├── calendar-context-menu.js
│       ├── calendar-event.js
│       └── calendar.js
├── package.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── pint.json
├── resources/
│   ├── css/
│   │   └── theme.css
│   ├── js/
│   │   ├── calendar-context-menu.js
│   │   ├── calendar-event.js
│   │   └── calendar.js
│   ├── lang/
│   │   ├── cs/
│   │   │   └── translations.php
│   │   ├── de/
│   │   │   └── translations.php
│   │   ├── en/
│   │   │   └── translations.php
│   │   └── fr/
│   │       └── translations.php
│   └── views/
│       ├── .gitkeep
│       ├── components/
│       │   └── context-menu.blade.php
│       └── widgets/
│           └── calendar-widget.blade.php
├── src/
│   ├── Attributes/
│   │   ├── CalendarEventContent.php
│   │   ├── CalendarResourceLabelContent.php
│   │   └── CalendarSchema.php
│   ├── CalendarPlugin.php
│   ├── CalendarServiceProvider.php
│   ├── Concerns/
│   │   ├── CalendarAction.php
│   │   ├── CanRefreshCalendar.php
│   │   ├── CanUseFilamentTimezone.php
│   │   ├── HandlesDateClick.php
│   │   ├── HandlesDateSelect.php
│   │   ├── HandlesDatesSet.php
│   │   ├── HandlesEventAllUpdated.php
│   │   ├── HandlesEventClick.php
│   │   ├── HandlesEventDragAndDrop.php
│   │   ├── HandlesEventResize.php
│   │   ├── HandlesNoEventsClick.php
│   │   ├── HandlesViewDidMount.php
│   │   ├── HasAuthorization.php
│   │   ├── HasCalendarContextData.php
│   │   ├── HasCalendarView.php
│   │   ├── HasContextMenu.php
│   │   ├── HasDayMaxEvents.php
│   │   ├── HasDefaultActions.php
│   │   ├── HasEventContent.php
│   │   ├── HasEvents.php
│   │   ├── HasFirstDay.php
│   │   ├── HasFooterActions.php
│   │   ├── HasHeaderActions.php
│   │   ├── HasHeading.php
│   │   ├── HasLocale.php
│   │   ├── HasMoreLinkContent.php
│   │   ├── HasNotifications.php
│   │   ├── HasOptions.php
│   │   ├── HasResourceLabelContent.php
│   │   ├── HasResources.php
│   │   ├── HasSchema.php
│   │   ├── HasTheme.php
│   │   ├── InteractsWithCalendar.php
│   │   └── InteractsWithEventRecord.php
│   ├── Contracts/
│   │   ├── ContextualInfo.php
│   │   ├── Eventable.php
│   │   ├── HasCalendar.php
│   │   └── Resourceable.php
│   ├── Enums/
│   │   ├── CalendarViewType.php
│   │   └── Context.php
│   ├── Exceptions/
│   │   └── SchemaNotFoundException.php
│   ├── Filament/
│   │   ├── Actions/
│   │   │   ├── CreateAction.php
│   │   │   ├── DeleteAction.php
│   │   │   ├── EditAction.php
│   │   │   └── ViewAction.php
│   │   └── CalendarWidget.php
│   ├── ValueObjects/
│   │   ├── CalendarEvent.php
│   │   ├── CalendarResource.php
│   │   ├── CalendarView.php
│   │   ├── DateClickInfo.php
│   │   ├── DateSelectInfo.php
│   │   ├── DatesSetInfo.php
│   │   ├── EventAllUpdatedInfo.php
│   │   ├── EventClickInfo.php
│   │   ├── EventDropInfo.php
│   │   ├── EventResizeInfo.php
│   │   ├── FetchInfo.php
│   │   ├── NoEventsClickInfo.php
│   │   └── ViewDidMountInfo.php
│   └── helpers.php
├── tests/
│   ├── ArchTest.php
│   ├── Pest.php
│   ├── TestCase.php
│   └── Unit/
│       └── ValueObjects/
│           └── EventTest.php
└── workbench/
    └── app/
        └── Providers/
            └── WorkbenchServiceProvider.php
Download .txt
SYMBOL INDEX (269 symbols across 74 files)

FILE: bin/build.js
  function compile (line 5) | async function compile(options) {

FILE: dist/js/calendar-context-menu.js
  function d (line 1) | function d({getContextMenuActionsUsing:o}){return{open:!1,size:{width:0,...

FILE: dist/js/calendar-event.js
  function o (line 1) | function o({event:t,timeText:r,view:d,hasContextMenu:n}){return{event:t,...

FILE: dist/js/calendar.js
  function M (line 1) | function M({view:c="dayGridMonth",locale:v="en",firstDay:f=1,dayMaxEvent...

FILE: resources/js/calendar-context-menu.js
  function calendarContextMenu (line 1) | function calendarContextMenu({

FILE: resources/js/calendar-event.js
  function calendarEvent (line 1) | function calendarEvent({

FILE: resources/js/calendar.js
  function calendar (line 1) | function calendar({

FILE: src/Attributes/CalendarEventContent.php
  class CalendarEventContent (line 7) | #[\Attribute(\Attribute::TARGET_METHOD)]
    method __construct (line 13) | public function __construct(public string $model) {}

FILE: src/Attributes/CalendarResourceLabelContent.php
  class CalendarResourceLabelContent (line 7) | #[\Attribute(\Attribute::TARGET_METHOD)]
    method __construct (line 13) | public function __construct(public string $model) {}

FILE: src/Attributes/CalendarSchema.php
  class CalendarSchema (line 7) | #[\Attribute(\Attribute::TARGET_METHOD)]
    method __construct (line 13) | public function __construct(public string $model) {}

FILE: src/CalendarPlugin.php
  class CalendarPlugin (line 8) | class CalendarPlugin implements Plugin
    method getId (line 10) | public function getId(): string
    method register (line 15) | public function register(Panel $panel): void {}
    method boot (line 17) | public function boot(Panel $panel): void {}
    method make (line 19) | public static function make(): static

FILE: src/CalendarServiceProvider.php
  class CalendarServiceProvider (line 12) | class CalendarServiceProvider extends PackageServiceProvider
    method configurePackage (line 14) | public function configurePackage(Package $package): void
    method packageBooted (line 28) | public function packageBooted(): void

FILE: src/Concerns/CalendarAction.php
  type CalendarAction (line 13) | trait CalendarAction
    method resolveDefaultClosureDependencyForEvaluationByType (line 15) | protected function resolveDefaultClosureDependencyForEvaluationByType(...

FILE: src/Concerns/CanRefreshCalendar.php
  type CanRefreshCalendar (line 5) | trait CanRefreshCalendar
    method refreshRecords (line 7) | public function refreshRecords(): static
    method refreshResources (line 14) | public function refreshResources(): static

FILE: src/Concerns/CanUseFilamentTimezone.php
  type CanUseFilamentTimezone (line 5) | trait CanUseFilamentTimezone
    method shouldUseFilamentTimezone (line 9) | public function shouldUseFilamentTimezone(): bool

FILE: src/Concerns/HandlesDateClick.php
  type HandlesDateClick (line 9) | trait HandlesDateClick
    method onDateClick (line 25) | protected function onDateClick(DateClickInfo $info): void {}
    method isDateClickEnabled (line 32) | public function isDateClickEnabled(): bool
    method onDateClickJs (line 40) | public function onDateClickJs(array $data): void

FILE: src/Concerns/HandlesDateSelect.php
  type HandlesDateSelect (line 8) | trait HandlesDateSelect
    method onDateSelect (line 24) | protected function onDateSelect(DateSelectInfo $info): void {}
    method isDateSelectEnabled (line 31) | public function isDateSelectEnabled(): bool
    method onDateSelectJs (line 39) | public function onDateSelectJs(array $data): void

FILE: src/Concerns/HandlesDatesSet.php
  type HandlesDatesSet (line 7) | trait HandlesDatesSet
    method onDatesSet (line 25) | protected function onDatesSet(DatesSetInfo $info): void {}
    method isDatesSetEnabled (line 32) | public function isDatesSetEnabled(): bool
    method onDatesSetJs (line 40) | public function onDatesSetJs(array $data): void

FILE: src/Concerns/HandlesEventAllUpdated.php
  type HandlesEventAllUpdated (line 7) | trait HandlesEventAllUpdated
    method onEventAllUpdated (line 11) | protected function onEventAllUpdated(EventAllUpdatedInfo $info): void {}
    method isEventAllUpdatedEnabled (line 13) | public function isEventAllUpdatedEnabled(): bool
    method onEventAllUpdatedJs (line 21) | public function onEventAllUpdatedJs(array $info): void

FILE: src/Concerns/HandlesEventClick.php
  type HandlesEventClick (line 9) | trait HandlesEventClick
    method onEventClick (line 18) | protected function onEventClick(EventClickInfo $info, Model $event, ?s...
    method isEventClickEnabled (line 28) | public function isEventClickEnabled(): bool
    method getDefaultEventClickAction (line 33) | public function getDefaultEventClickAction(): ?string
    method onEventClickJs (line 41) | public function onEventClickJs(array $data = [], ?string $action = nul...

FILE: src/Concerns/HandlesEventDragAndDrop.php
  type HandlesEventDragAndDrop (line 9) | trait HandlesEventDragAndDrop
    method onEventDrop (line 19) | protected function onEventDrop(EventDropInfo $info, Model $event): bool
    method isEventDragEnabled (line 24) | public function isEventDragEnabled(): bool
    method onEventDropJs (line 32) | public function onEventDropJs(array $data): bool

FILE: src/Concerns/HandlesEventResize.php
  type HandlesEventResize (line 9) | trait HandlesEventResize
    method onEventResize (line 19) | protected function onEventResize(EventResizeInfo $info, Model $event):...
    method isEventResizeEnabled (line 24) | public function isEventResizeEnabled(): bool
    method onEventResizeJs (line 32) | public function onEventResizeJs(array $data): bool

FILE: src/Concerns/HandlesNoEventsClick.php
  type HandlesNoEventsClick (line 8) | trait HandlesNoEventsClick
    method onNoEventsClick (line 12) | protected function onNoEventsClick(NoEventsClickInfo $info): void {}
    method isNoEventsClickEnabled (line 14) | public function isNoEventsClickEnabled(): bool
    method onNoEventsClickJs (line 22) | public function onNoEventsClickJs(array $data): void

FILE: src/Concerns/HandlesViewDidMount.php
  type HandlesViewDidMount (line 7) | trait HandlesViewDidMount
    method onViewDidMount (line 11) | protected function onViewDidMount(ViewDidMountInfo $info): void {}
    method isViewDidMountEnabled (line 13) | public function isViewDidMountEnabled(): bool
    method onViewDidMountJs (line 21) | public function onViewDidMountJs(array $data): void

FILE: src/Concerns/HasAuthorization.php
  type HasAuthorization (line 10) | trait HasAuthorization
    method shouldSkipAuthorization (line 14) | public function shouldSkipAuthorization(): bool
    method getAuthorizationResponse (line 19) | public function getAuthorizationResponse(string $action, null | Model ...
    method transformActionName (line 35) | protected function transformActionName(string $action): string

FILE: src/Concerns/HasCalendarContextData.php
  type HasCalendarContextData (line 14) | trait HasCalendarContextData
    method setRawCalendarContextData (line 18) | protected function setRawCalendarContextData(Context $context, array $...
    method getRawCalendarContextData (line 31) | public function getRawCalendarContextData(?string $key = null): array ...
    method getCalendarContextInfo (line 44) | public function getCalendarContextInfo(): ?ContextualInfo

FILE: src/Concerns/HasCalendarView.php
  type HasCalendarView (line 7) | trait HasCalendarView
    method getCalendarView (line 11) | public function getCalendarView(): CalendarViewType

FILE: src/Concerns/HasContextMenu.php
  type HasContextMenu (line 10) | trait HasContextMenu
    method bootedHasContextMenu (line 14) | public function bootedHasContextMenu(): void
    method getContextMenuActionsUsing (line 19) | public function getContextMenuActionsUsing(Context $context, array $da...
    method hasContextMenu (line 40) | public function hasContextMenu(?Context $context = null): bool
    method cacheContextMenuActions (line 51) | protected function cacheContextMenuActions(): void
    method getCachedContextMenuActions (line 67) | protected function getCachedContextMenuActions(): array
    method getDateClickContextMenuActions (line 72) | protected function getDateClickContextMenuActions(): array
    method getDateSelectContextMenuActions (line 77) | protected function getDateSelectContextMenuActions(): array
    method getEventClickContextMenuActions (line 82) | protected function getEventClickContextMenuActions(): array
    method getNoEventsClickContextMenuActions (line 87) | protected function getNoEventsClickContextMenuActions(): array
    method getCachedDateClickContextMenuActions (line 92) | protected function getCachedDateClickContextMenuActions(): array
    method getCachedDateSelectContextMenuActions (line 97) | protected function getCachedDateSelectContextMenuActions(): array
    method getCachedEventClickContextMenuActions (line 102) | protected function getCachedEventClickContextMenuActions(): array
    method getCachedNoEventsClickContextMenuActions (line 107) | protected function getCachedNoEventsClickContextMenuActions(): array
    method cacheContextMenuAction (line 112) | private function cacheContextMenuAction(Action $action, Context $conte...

FILE: src/Concerns/HasDayMaxEvents.php
  type HasDayMaxEvents (line 5) | trait HasDayMaxEvents
    method getDayMaxEvents (line 23) | public function getDayMaxEvents(): bool

FILE: src/Concerns/HasDefaultActions.php
  type HasDefaultActions (line 12) | trait HasDefaultActions
    method createAction (line 19) | protected function createAction(string $model, ?string $name = null): ...
    method viewAction (line 30) | public function viewAction(): ViewAction
    method editAction (line 35) | public function editAction(): EditAction
    method deleteAction (line 40) | public function deleteAction(): DeleteAction

FILE: src/Concerns/HasEventContent.php
  type HasEventContent (line 9) | trait HasEventContent
    method getEventContentJs (line 11) | public function getEventContentJs(): ?array

FILE: src/Concerns/HasEvents.php
  type HasEvents (line 11) | trait HasEvents
    method getEvents (line 13) | abstract protected function getEvents(FetchInfo $info): Collection | a...
    method getEventsJs (line 15) | public function getEventsJs(array $info): array

FILE: src/Concerns/HasFirstDay.php
  type HasFirstDay (line 7) | trait HasFirstDay
    method getFirstDay (line 17) | public function getFirstDay(): WeekDay

FILE: src/Concerns/HasFooterActions.php
  type HasFooterActions (line 11) | trait HasFooterActions
    method bootedHasFooterActions (line 15) | public function bootedHasFooterActions(): void
    method cacheFooterActions (line 20) | protected function cacheFooterActions(): void
    method getCachedFooterActions (line 46) | public function getCachedFooterActions(): array
    method getFooterActions (line 51) | public function getFooterActions(): array
    method getCachedFooterActionsComponent (line 56) | public function getCachedFooterActionsComponent(): Actions

FILE: src/Concerns/HasHeaderActions.php
  type HasHeaderActions (line 11) | trait HasHeaderActions
    method bootedHasHeaderActions (line 15) | public function bootedHasHeaderActions(): void
    method cacheHeaderActions (line 20) | protected function cacheHeaderActions(): void
    method getCachedHeaderActions (line 45) | public function getCachedHeaderActions(): array
    method getHeaderActions (line 50) | public function getHeaderActions(): array
    method getCachedHeaderActionsComponent (line 55) | public function getCachedHeaderActionsComponent(): Actions

FILE: src/Concerns/HasHeading.php
  type HasHeading (line 7) | trait HasHeading
    method getHeading (line 11) | public function getHeading(): null | string | HtmlString

FILE: src/Concerns/HasLocale.php
  type HasLocale (line 5) | trait HasLocale
    method getLocale (line 15) | public function getLocale(): string
    method getDefaultLocale (line 20) | private function getDefaultLocale(): string

FILE: src/Concerns/HasMoreLinkContent.php
  type HasMoreLinkContent (line 7) | trait HasMoreLinkContent
    method getMoreLinkContent (line 17) | public function getMoreLinkContent(): null | string | array
    method getMoreLinkContentJs (line 22) | public function getMoreLinkContentJs(): ?string

FILE: src/Concerns/HasNotifications.php
  type HasNotifications (line 8) | trait HasNotifications
    method sendUnauthorizedNotification (line 10) | public function sendUnauthorizedNotification(Response $response): static
    method getUnauthorizedNotification (line 21) | protected function getUnauthorizedNotification(Response $response): ?N...
    method getUnauthorizedNotificationTitle (line 30) | protected function getUnauthorizedNotificationTitle(Response $response...

FILE: src/Concerns/HasOptions.php
  type HasOptions (line 5) | trait HasOptions
    method getOptions (line 12) | public function getOptions(): array
    method setOption (line 17) | public function setOption(string $key, mixed $value): static

FILE: src/Concerns/HasResourceLabelContent.php
  type HasResourceLabelContent (line 9) | trait HasResourceLabelContent
    method getResourceLabelContentJs (line 11) | public function getResourceLabelContentJs(): ?array

FILE: src/Concerns/HasResources.php
  type HasResources (line 10) | trait HasResources
    method getResources (line 12) | protected function getResources(): Collection | array | Builder
    method getResourcesJs (line 17) | public function getResourcesJs(): array

FILE: src/Concerns/HasSchema.php
  type HasSchema (line 14) | trait HasSchema
    method getSchemaForModel (line 19) | public function getSchemaForModel(Schema $schema, ?string $model = nul...
    method getFormSchemaForModel (line 59) | public function getFormSchemaForModel(Schema $schema, ?string $model =...
    method getInfolistSchemaForModel (line 77) | public function getInfolistSchemaForModel(Schema $schema, ?string $mod...

FILE: src/Concerns/HasTheme.php
  type HasTheme (line 5) | trait HasTheme
    method getTheme (line 7) | public function getTheme(): ?array

FILE: src/Concerns/InteractsWithCalendar.php
  type InteractsWithCalendar (line 9) | trait InteractsWithCalendar
    method mountAction (line 47) | public function mountAction(string $name, array $arguments = [], array...

FILE: src/Concerns/InteractsWithEventRecord.php
  type InteractsWithEventRecord (line 11) | trait InteractsWithEventRecord
    method getEventRecord (line 16) | public function getEventRecord(): ?Model
    method getEventModel (line 21) | public function getEventModel(): ?string
    method resolveEventRecord (line 30) | protected function resolveEventRecord(): ?Model
    method resolveEventRecordRouteBinding (line 47) | protected function resolveEventRecordRouteBinding(string $model, mixed...
    method getEloquentQuery (line 55) | protected function getEloquentQuery(string $model): Builder
    method getEventRecordRouteKeyName (line 60) | protected function getEventRecordRouteKeyName(?string $model = null): ...

FILE: src/Contracts/ContextualInfo.php
  type ContextualInfo (line 7) | interface ContextualInfo
    method getContext (line 9) | public function getContext(): Context;

FILE: src/Contracts/Eventable.php
  type Eventable (line 7) | interface Eventable
    method toCalendarEvent (line 9) | public function toCalendarEvent(): CalendarEvent;

FILE: src/Contracts/HasCalendar.php
  type HasCalendar (line 5) | interface HasCalendar {}

FILE: src/Contracts/Resourceable.php
  type Resourceable (line 7) | interface Resourceable
    method toCalendarResource (line 9) | public function toCalendarResource(): CalendarResource;

FILE: src/Enums/Context.php
  method interactsWithRecord (line 14) | public function interactsWithRecord(): bool

FILE: src/Exceptions/SchemaNotFoundException.php
  class SchemaNotFoundException (line 7) | class SchemaNotFoundException extends Exception {}

FILE: src/Filament/Actions/CreateAction.php
  class CreateAction (line 9) | class CreateAction extends \Filament\Actions\CreateAction
    method setUp (line 13) | protected function setUp(): void

FILE: src/Filament/Actions/DeleteAction.php
  class DeleteAction (line 9) | class DeleteAction extends \Filament\Actions\DeleteAction
    method setUp (line 13) | protected function setUp(): void

FILE: src/Filament/Actions/EditAction.php
  class EditAction (line 8) | class EditAction extends \Filament\Actions\EditAction
    method setUp (line 10) | protected function setUp(): void

FILE: src/Filament/Actions/ViewAction.php
  class ViewAction (line 8) | class ViewAction extends \Filament\Actions\ViewAction
    method setUp (line 10) | protected function setUp(): void

FILE: src/Filament/CalendarWidget.php
  class CalendarWidget (line 11) | abstract class CalendarWidget extends Widget implements HasActions, HasC...

FILE: src/ValueObjects/CalendarEvent.php
  class CalendarEvent (line 15) | class CalendarEvent
    method __construct (line 47) | private function __construct(?Model $model = null)
    method start (line 55) | public function start(string | Carbon $start): static
    method getStart (line 67) | public function getStart(): Carbon
    method end (line 72) | public function end(string | Carbon $end): static
    method getEnd (line 84) | public function getEnd(): Carbon
    method allDay (line 89) | public function allDay(bool $allDay = true): static
    method getAllDay (line 96) | public function getAllDay(): bool
    method title (line 101) | public function title(string | Htmlable $title): static
    method getTitle (line 108) | public function getTitle(): string | Htmlable
    method backgroundColor (line 115) | public function backgroundColor(string $color): static
    method getBackgroundColor (line 122) | public function getBackgroundColor(): ?string
    method textColor (line 127) | public function textColor(string $color): static
    method getTextColor (line 134) | public function getTextColor(): ?string
    method classes (line 139) | public function classes(array $classes): static
    method classNames (line 144) | public function classNames(array $classNames): static
    method getClassNames (line 151) | public function getClassNames(): string
    method styles (line 156) | public function styles(array $styles): static
    method getStyles (line 163) | public function getStyles(): string
    method display (line 180) | public function display(string $display): static
    method getDisplay (line 187) | public function getDisplay(): ?string
    method displayAuto (line 192) | public function displayAuto(): static
    method displayBackground (line 197) | public function displayBackground(): static
    method editable (line 202) | public function editable(bool $editable = true): static
    method getEditable (line 209) | public function getEditable(): ?bool
    method startEditable (line 214) | public function startEditable(bool $startEditable = true): static
    method getStartEditable (line 221) | public function getStartEditable(): ?bool
    method durationEditable (line 226) | public function durationEditable(bool $durationEditable = true): static
    method getDurationEditable (line 233) | public function getDurationEditable(): ?bool
    method resourceId (line 238) | public function resourceId(int | string | CalendarResource $resource):...
    method resourceIds (line 245) | public function resourceIds(array $resourceIds): static
    method getResourceIds (line 255) | public function getResourceIds(): array
    method url (line 260) | public function url(string $url, string $target = '_blank'): static
    method key (line 268) | public function key(string $key): static
    method model (line 275) | public function model(string $model): static
    method action (line 282) | public function action(string $action): static
    method extendedProp (line 289) | public function extendedProp(string $key, mixed $value): static
    method extendedProps (line 296) | public function extendedProps(array $props): static
    method timezone (line 306) | public function timezone(string $timezone): static
    method getExtendedProps (line 313) | public function getExtendedProps(): array
    method make (line 318) | public static function make(?Model $model = null): static
    method toCalendarObject (line 323) | public function toCalendarObject(int $timezoneOffset, bool $useFilamen...
    method fromCalendarObject (line 361) | public function fromCalendarObject(array $data, int $timezoneOffset, b...

FILE: src/ValueObjects/CalendarResource.php
  class CalendarResource (line 8) | class CalendarResource
    method __construct (line 22) | private function __construct(Model | int | string $id)
    method getId (line 31) | public function getId(): int | string
    method title (line 36) | public function title(string | Htmlable $title): static
    method getTitle (line 43) | public function getTitle(): string
    method eventBackgroundColor (line 50) | public function eventBackgroundColor(string $color): static
    method getEventBackgroundColor (line 57) | public function getEventBackgroundColor(): ?string
    method eventTextColor (line 62) | public function eventTextColor(string $color): static
    method getEventTextColor (line 69) | public function getEventTextColor(): ?string
    method child (line 74) | public function child(array | CalendarResource $child): static
    method children (line 79) | public function children(array $children): static
    method getChildren (line 89) | public function getChildren(): array
    method extendedProp (line 94) | public function extendedProp(string $key, mixed $value): static
    method extendedProps (line 101) | public function extendedProps(array $props): static
    method getExtendedProps (line 111) | public function getExtendedProps(): array
    method make (line 116) | public static function make(Model | int | string $id): static
    method toCalendarObject (line 121) | public function toCalendarObject(): array
    method fromCalendarObject (line 133) | public static function fromCalendarObject(array $data): CalendarResource

FILE: src/ValueObjects/CalendarView.php
  class CalendarView (line 10) | readonly class CalendarView
    method __construct (line 28) | public function __construct(array $data, int $timezoneOffset, bool $us...

FILE: src/ValueObjects/DateClickInfo.php
  class DateClickInfo (line 11) | readonly class DateClickInfo implements ContextualInfo
    method __construct (line 23) | public function __construct(array $data, bool $useFilamentTimezone)
    method getContext (line 48) | public function getContext(): Context

FILE: src/ValueObjects/DateSelectInfo.php
  class DateSelectInfo (line 11) | readonly class DateSelectInfo implements ContextualInfo
    method __construct (line 25) | public function __construct(array $data, bool $useFilamentTimezone)
    method getContext (line 56) | public function getContext(): Context

FILE: src/ValueObjects/DatesSetInfo.php
  class DatesSetInfo (line 9) | readonly class DatesSetInfo
    method __construct (line 19) | public function __construct(array $data, bool $useFilamentTimezone)

FILE: src/ValueObjects/EventAllUpdatedInfo.php
  class EventAllUpdatedInfo (line 5) | readonly class EventAllUpdatedInfo
    method __construct (line 11) | public function __construct(array $data, bool $useFilamentTimezone)

FILE: src/ValueObjects/EventClickInfo.php
  class EventClickInfo (line 9) | readonly class EventClickInfo implements ContextualInfo
    method __construct (line 19) | public function __construct(array $data, Model $record, bool $useFilam...
    method getContext (line 40) | public function getContext(): Context

FILE: src/ValueObjects/EventDropInfo.php
  class EventDropInfo (line 9) | readonly class EventDropInfo implements ContextualInfo
    method __construct (line 21) | public function __construct(array $data, Model $record, bool $useFilam...
    method getContext (line 51) | public function getContext(): Context

FILE: src/ValueObjects/EventResizeInfo.php
  class EventResizeInfo (line 9) | readonly class EventResizeInfo implements ContextualInfo
    method __construct (line 21) | public function __construct(array $data, Model $record, bool $useFilam...
    method getContext (line 50) | public function getContext(): Context

FILE: src/ValueObjects/FetchInfo.php
  class FetchInfo (line 9) | readonly class FetchInfo
    method __construct (line 17) | public function __construct(array $data)

FILE: src/ValueObjects/NoEventsClickInfo.php
  class NoEventsClickInfo (line 8) | readonly class NoEventsClickInfo implements ContextualInfo
    method __construct (line 14) | public function __construct(array $data, bool $useFilamentTimezone)
    method getContext (line 25) | public function getContext(): Context

FILE: src/ValueObjects/ViewDidMountInfo.php
  class ViewDidMountInfo (line 5) | readonly class ViewDidMountInfo
    method __construct (line 11) | public function __construct(array $data, bool $useFilamentTimezone)

FILE: src/helpers.php
  function browser_date_to_user_date (line 17) | function browser_date_to_user_date(CarbonImmutable | string $date): Carb...
  function browser_date_to_app_date (line 36) | function browser_date_to_app_date(CarbonImmutable | string $date): Carbo...
  function utc_to_user_local_time (line 55) | function utc_to_user_local_time(CarbonImmutable | string $date, int $tim...

FILE: tests/Pest.php
  function something (line 42) | function something()

FILE: tests/TestCase.php
  class TestCase (line 9) | abstract class TestCase extends BaseTestCase
    method setUp (line 11) | protected function setUp(): void
    method getPackageProviders (line 20) | protected function getPackageProviders($app)
    method getEnvironmentSetUp (line 27) | public function getEnvironmentSetUp($app)

FILE: workbench/app/Providers/WorkbenchServiceProvider.php
  class WorkbenchServiceProvider (line 8) | class WorkbenchServiceProvider extends ServiceProvider
    method register (line 13) | public function register(): void
    method boot (line 21) | public function boot(): void
Condensed preview — 111 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (201K chars).
[
  {
    "path": ".editorconfig",
    "chars": 220,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".gitattributes",
    "chars": 773,
    "preview": "# Path-based git attributes\n# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html\n\n# Ignore all test and"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2972,
    "preview": "# Contributing\n\nContributions are **welcome** and will be fully **credited**.\n\nPlease read and understand the contributi"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 16,
    "preview": "github: GuavaCZ\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 2174,
    "preview": "name: Bug Report\ndescription: Report an Issue or Bug with the Package\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nbody:\n    - type:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 472,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Ask a question\n    url: https://github.com/GuavaCZ/calendar/discuss"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 566,
    "preview": "name: Feature Request\ndescription: Create a feature request\ntitle: \"[Request]: \"\nbody:\n    - type: textarea\n      id: fe"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 129,
    "preview": "# Security Policy\n\nIf you discover any security related issues, please email office@guava.cz instead of using the issue "
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "chars": 493,
    "preview": "name: PHPStan\n\non:\n  push:\n    paths:\n      - '**.php'\n      - 'phpstan.neon.dist'\n\njobs:\n  phpstan:\n    name: phpstan\n "
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1582,
    "preview": "name: Release\n\non:\n  push:\n    branches:\n      - master\n      - main\n      - beta\n      - alpha\n      - ?.[xX]\n      - ?"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "chars": 1685,
    "preview": "name: run-tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  test:\n    ru"
  },
  {
    "path": ".gitignore",
    "chars": 110,
    "preview": ".idea\n.phpunit.cache\nbuild\ncomposer.lock\ncoverage\nphpunit.xml\nphpstan.neon\ntestbench.yaml\nvendor\nnode_modules\n"
  },
  {
    "path": ".gitmodules",
    "chars": 95,
    "preview": "[submodule \"demo\"]\n\tpath = demo\n\turl = https://github.com/GuavaCZ/calendar-demo\n\tbranch = main\n"
  },
  {
    "path": ".releaserc.json",
    "chars": 2121,
    "preview": "{\n    \"plugins\": [\n        [\n            \"@semantic-release/commit-analyzer\",\n            {\n                \"preset\": \"c"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 80,
    "preview": "# Changelog\n\nAll notable changes to `calendar` will be documented in this file.\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1089,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Guava <lukas.frey@guava.cz>\n\nPermission is hereby granted, free of charge, to any p"
  },
  {
    "path": "README.md",
    "chars": 49224,
    "preview": "![calendar Banner](https://github.com/GuavaCZ/calendar/raw/main/docs/images/banner.jpg)\n\n\n# Adds support for vkurko/cale"
  },
  {
    "path": "bin/build.js",
    "chars": 2034,
    "preview": "import esbuild from 'esbuild'\n\nconst isDev = process.argv.includes('--dev')\n\nasync function compile(options) {\n    const"
  },
  {
    "path": "composer.json",
    "chars": 2622,
    "preview": "{\n    \"name\": \"guava/calendar\",\n    \"description\": \"Adds support for vkurko/calendar to Filament PHP.\",\n    \"keywords\": "
  },
  {
    "path": "config/calendar.php",
    "chars": 49,
    "preview": "<?php\n\n// config for Guava/Calendar\nreturn [\n\n];\n"
  },
  {
    "path": "database/factories/ModelFactory.php",
    "chars": 267,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\n\n/*\nclass Model"
  },
  {
    "path": "database/migrations/create_calendar_table.php.stub",
    "chars": 383,
    "preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
  },
  {
    "path": "dist/js/calendar-context-menu.js",
    "chars": 1476,
    "preview": "function d({getContextMenuActionsUsing:o}){return{open:!1,size:{width:0,height:0},position:{x:0,y:0},mountData:{},contex"
  },
  {
    "path": "dist/js/calendar-event.js",
    "chars": 989,
    "preview": "function o({event:t,timeText:r,view:d,hasContextMenu:n}){return{event:t,contextMenu:null,init:function(){n&&this.initial"
  },
  {
    "path": "dist/js/calendar.js",
    "chars": 3744,
    "preview": "function M({view:c=\"dayGridMonth\",locale:v=\"en\",firstDay:f=1,dayMaxEvents:w=!1,eventContent:l=null,eventClickEnabled:m=!"
  },
  {
    "path": "package.json",
    "chars": 401,
    "preview": "{\n    \"devDependencies\": {\n        \"@event-calendar/core\": \"^4.5.0\",\n        \"conventional-changelog-conventionalcommits"
  },
  {
    "path": "phpstan-baseline.neon",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 309,
    "preview": "includes:\n    - phpstan-baseline.neon\n    - ./vendor/nunomaduro/larastan/extension.neon\n\nparameters:\n    level: 4\n    pa"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1124,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noName"
  },
  {
    "path": "pint.json",
    "chars": 302,
    "preview": "{\n    \"preset\": \"laravel\",\n    \"rules\": {\n        \"method_argument_space\": true,\n        \"multiline_whitespace_before_se"
  },
  {
    "path": "resources/css/theme.css",
    "chars": 2968,
    "preview": ".ec {\n    --ec-list-day-bg-color: white;\n    --ec-today-bg-color: var(--color-50);\n\n    /*--ec-text-color: var(--gray-50"
  },
  {
    "path": "resources/js/calendar-context-menu.js",
    "chars": 3049,
    "preview": "export default function calendarContextMenu({\n                                                getContextMenuActionsUsing"
  },
  {
    "path": "resources/js/calendar-event.js",
    "chars": 2302,
    "preview": "export default function calendarEvent({\n                                          event,\n                               "
  },
  {
    "path": "resources/js/calendar.js",
    "chars": 10476,
    "preview": "export default function calendar({\n                                     view = 'dayGridMonth',\n                         "
  },
  {
    "path": "resources/lang/cs/translations.php",
    "chars": 48,
    "preview": "<?php\n\nreturn [\n    'heading' => 'Kalendář',\n];\n"
  },
  {
    "path": "resources/lang/de/translations.php",
    "chars": 48,
    "preview": "<?php\n\nreturn [\n    'heading' => 'Kalender',\n];\n"
  },
  {
    "path": "resources/lang/en/translations.php",
    "chars": 48,
    "preview": "<?php\n\nreturn [\n    'heading' => 'Calendar',\n];\n"
  },
  {
    "path": "resources/lang/fr/translations.php",
    "chars": 50,
    "preview": "<?php\n\nreturn [\n    'heading' => 'Calendrier',\n];\n"
  },
  {
    "path": "resources/views/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "resources/views/components/context-menu.blade.php",
    "chars": 3211,
    "preview": "@php\n    use Filament\\Support\\Facades\\FilamentAsset;\n    use Guava\\Calendar\\Enums\\Context;\n\n    use function Filament\\Su"
  },
  {
    "path": "resources/views/widgets/calendar-widget.blade.php",
    "chars": 2888,
    "preview": "@php\n    use Filament\\Support\\Facades\\FilamentAsset;\n    use Guava\\Calendar\\Enums\\Context;\n    use Filament\\Support\\Faca"
  },
  {
    "path": "src/Attributes/CalendarEventContent.php",
    "chars": 274,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Attributes;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\n#[\\Attribute(\\Attribute::TARGET_ME"
  },
  {
    "path": "src/Attributes/CalendarResourceLabelContent.php",
    "chars": 282,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Attributes;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\n#[\\Attribute(\\Attribute::TARGET_ME"
  },
  {
    "path": "src/Attributes/CalendarSchema.php",
    "chars": 268,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Attributes;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\n#[\\Attribute(\\Attribute::TARGET_ME"
  },
  {
    "path": "src/CalendarPlugin.php",
    "chars": 402,
    "preview": "<?php\n\nnamespace Guava\\Calendar;\n\nuse Filament\\Contracts\\Plugin;\nuse Filament\\Panel;\n\nclass CalendarPlugin implements Pl"
  },
  {
    "path": "src/CalendarServiceProvider.php",
    "chars": 1618,
    "preview": "<?php\n\nnamespace Guava\\Calendar;\n\nuse Filament\\Support\\Assets\\AlpineComponent;\nuse Filament\\Support\\Assets\\Css;\nuse Fila"
  },
  {
    "path": "src/Concerns/CalendarAction.php",
    "chars": 1539,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Contracts\\Has"
  },
  {
    "path": "src/Concerns/CanRefreshCalendar.php",
    "chars": 345,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait CanRefreshCalendar\n{\n    public function refreshRecords(): static\n    {"
  },
  {
    "path": "src/Concerns/CanUseFilamentTimezone.php",
    "chars": 235,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait CanUseFilamentTimezone\n{\n    protected bool $useFilamentTimezone = fals"
  },
  {
    "path": "src/Concerns/HandlesDateClick.php",
    "chars": 1470,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\DateClickIn"
  },
  {
    "path": "src/Concerns/HandlesDateSelect.php",
    "chars": 1449,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\DateSelectI"
  },
  {
    "path": "src/Concerns/HandlesDatesSet.php",
    "chars": 1550,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\ValueObjects\\DatesSetInfo;\n\ntrait HandlesDatesSet\n{\n    /*"
  },
  {
    "path": "src/Concerns/HandlesEventAllUpdated.php",
    "chars": 776,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\ValueObjects\\EventAllUpdatedInfo;\n\ntrait HandlesEventAllUp"
  },
  {
    "path": "src/Concerns/HandlesEventClick.php",
    "chars": 2586,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\EventClickI"
  },
  {
    "path": "src/Concerns/HandlesEventDragAndDrop.php",
    "chars": 1257,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\EventDropIn"
  },
  {
    "path": "src/Concerns/HandlesEventResize.php",
    "chars": 1260,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\EventResize"
  },
  {
    "path": "src/Concerns/HandlesNoEventsClick.php",
    "chars": 827,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\Context;\nuse Guava\\Calendar\\ValueObjects\\NoEventsCli"
  },
  {
    "path": "src/Concerns/HandlesViewDidMount.php",
    "chars": 741,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\ValueObjects\\ViewDidMountInfo;\n\ntrait HandlesViewDidMount\n"
  },
  {
    "path": "src/Concerns/HasAuthorization.php",
    "chars": 1179,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Illuminate\\Auth\\Access\\Response;\nuse Illuminate\\Database\\Eloquent\\Model;\n"
  },
  {
    "path": "src/Concerns/HasCalendarContextData.php",
    "chars": 2424,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Enums\\Context"
  },
  {
    "path": "src/Concerns/HasCalendarView.php",
    "chars": 297,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Enums\\CalendarViewType;\n\ntrait HasCalendarView\n{\n    prote"
  },
  {
    "path": "src/Concerns/HasContextMenu.php",
    "chars": 4256,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Actions\\Action;\nuse Guava\\Calendar\\Enums\\Context;\nuse Illuminate"
  },
  {
    "path": "src/Concerns/HasDayMaxEvents.php",
    "chars": 962,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait HasDayMaxEvents\n{\n    /**\n     * Determines the maximum number of stack"
  },
  {
    "path": "src/Concerns/HasDefaultActions.php",
    "chars": 1099,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Actions\\Action;\nuse Guava\\Calendar\\Filament\\Actions\\CreateAction"
  },
  {
    "path": "src/Concerns/HasEventContent.php",
    "chars": 1339,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Attributes\\CalendarEventContent;\nuse Illuminate\\Contracts\\"
  },
  {
    "path": "src/Concerns/HasEvents.php",
    "chars": 1132,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Contracts\\Eventable;\nuse Guava\\Calendar\\ValueObjects\\Calen"
  },
  {
    "path": "src/Concerns/HasFirstDay.php",
    "chars": 443,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Carbon\\WeekDay;\n\ntrait HasFirstDay\n{\n    /**\n     * The day that each wee"
  },
  {
    "path": "src/Concerns/HasFooterActions.php",
    "chars": 1599,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\ActionGroup;\nuse Filament\\S"
  },
  {
    "path": "src/Concerns/HasHeaderActions.php",
    "chars": 1598,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\ActionGroup;\nuse Filament\\S"
  },
  {
    "path": "src/Concerns/HasHeading.php",
    "chars": 486,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Illuminate\\Support\\HtmlString;\n\ntrait HasHeading\n{\n    protected string |"
  },
  {
    "path": "src/Concerns/HasLocale.php",
    "chars": 812,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait HasLocale\n{\n    /**\n     * Defines the locale parameter for the native "
  },
  {
    "path": "src/Concerns/HasMoreLinkContent.php",
    "chars": 616,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Illuminate\\View\\View;\n\ntrait HasMoreLinkContent\n{\n    protected ?string $"
  },
  {
    "path": "src/Concerns/HasNotifications.php",
    "chars": 830,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Notifications\\Notification;\nuse Illuminate\\Auth\\Access\\Response;"
  },
  {
    "path": "src/Concerns/HasOptions.php",
    "chars": 483,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait HasOptions\n{\n    protected array $options = [];\n\n    /**\n     * Allows "
  },
  {
    "path": "src/Concerns/HasResourceLabelContent.php",
    "chars": 1435,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Attributes\\CalendarResourceLabelContent;\nuse Illuminate\\Co"
  },
  {
    "path": "src/Concerns/HasResources.php",
    "chars": 988,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Guava\\Calendar\\Contracts\\Resourceable;\nuse Guava\\Calendar\\ValueObjects\\Ca"
  },
  {
    "path": "src/Concerns/HasSchema.php",
    "chars": 2784,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Facades\\Filament;\nuse Filament\\Resources\\Resource;\nuse Filament\\"
  },
  {
    "path": "src/Concerns/HasTheme.php",
    "chars": 132,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\ntrait HasTheme\n{\n    public function getTheme(): ?array\n    {\n        return "
  },
  {
    "path": "src/Concerns/InteractsWithCalendar.php",
    "chars": 1428,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Filament\\Actions\\Concerns\\InteractsWithActions;\nuse Filament\\Schemas\\Conc"
  },
  {
    "path": "src/Concerns/InteractsWithEventRecord.php",
    "chars": 1767,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Concerns;\n\nuse Exception;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Datab"
  },
  {
    "path": "src/Contracts/ContextualInfo.php",
    "chars": 151,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Contracts;\n\nuse Guava\\Calendar\\Enums\\Context;\n\ninterface ContextualInfo\n{\n    public fun"
  },
  {
    "path": "src/Contracts/Eventable.php",
    "chars": 170,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Contracts;\n\nuse Guava\\Calendar\\ValueObjects\\CalendarEvent;\n\ninterface Eventable\n{\n    pu"
  },
  {
    "path": "src/Contracts/HasCalendar.php",
    "chars": 69,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Contracts;\n\ninterface HasCalendar {}\n"
  },
  {
    "path": "src/Contracts/Resourceable.php",
    "chars": 182,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Contracts;\n\nuse Guava\\Calendar\\ValueObjects\\CalendarResource;\n\ninterface Resourceable\n{\n"
  },
  {
    "path": "src/Enums/CalendarViewType.php",
    "chars": 599,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Enums;\n\nenum CalendarViewType: string\n{\n    case DayGridMonth = 'dayGridMonth';\n    case"
  },
  {
    "path": "src/Enums/Context.php",
    "chars": 512,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Enums;\n\nenum Context: string\n{\n    case DateClick = 'dateClick';\n    case DateSelect = '"
  },
  {
    "path": "src/Exceptions/SchemaNotFoundException.php",
    "chars": 112,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Exceptions;\n\nuse Exception;\n\nclass SchemaNotFoundException extends Exception {}\n"
  },
  {
    "path": "src/Filament/Actions/CreateAction.php",
    "chars": 672,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Filament\\Actions;\n\nuse Filament\\Schemas\\Schema;\nuse Guava\\Calendar\\Concerns\\CalendarActi"
  },
  {
    "path": "src/Filament/Actions/DeleteAction.php",
    "chars": 983,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Filament\\Actions;\n\nuse Guava\\Calendar\\Concerns\\CalendarAction;\nuse Guava\\Calendar\\Contra"
  },
  {
    "path": "src/Filament/Actions/EditAction.php",
    "chars": 808,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Filament\\Actions;\n\nuse Filament\\Schemas\\Schema;\nuse Guava\\Calendar\\Contracts\\HasCalendar"
  },
  {
    "path": "src/Filament/Actions/ViewAction.php",
    "chars": 739,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Filament\\Actions;\n\nuse Filament\\Schemas\\Schema;\nuse Guava\\Calendar\\Contracts\\HasCalendar"
  },
  {
    "path": "src/Filament/CalendarWidget.php",
    "chars": 510,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Filament;\n\nuse Filament\\Actions\\Contracts\\HasActions;\nuse Filament\\Schemas\\Contracts\\Has"
  },
  {
    "path": "src/ValueObjects/CalendarEvent.php",
    "chars": 9433,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\Carbon;\nuse Filament\\Support\\Facades\\FilamentTimezone;\nuse Ill"
  },
  {
    "path": "src/ValueObjects/CalendarResource.php",
    "chars": 3692,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Illuminate\\Contracts\\Support\\Htmlable;\nuse Illuminate\\Database\\Eloque"
  },
  {
    "path": "src/ValueObjects/CalendarView.php",
    "chars": 1259,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\CarbonImmutable;\nuse Guava\\Calendar\\Enums\\CalendarViewType;\n\nu"
  },
  {
    "path": "src/ValueObjects/DateClickInfo.php",
    "chars": 1230,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\CarbonImmutable;\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\n"
  },
  {
    "path": "src/ValueObjects/DateSelectInfo.php",
    "chars": 1436,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\CarbonImmutable;\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\n"
  },
  {
    "path": "src/ValueObjects/DatesSetInfo.php",
    "chars": 921,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\CarbonImmutable;\n\nuse function Guava\\Calendar\\utc_to_user_loca"
  },
  {
    "path": "src/ValueObjects/EventAllUpdatedInfo.php",
    "chars": 438,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nreadonly class EventAllUpdatedInfo\n{\n    public CalendarView $view;\n\n    "
  },
  {
    "path": "src/ValueObjects/EventClickInfo.php",
    "chars": 1015,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Enums\\Con"
  },
  {
    "path": "src/ValueObjects/EventDropInfo.php",
    "chars": 1341,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Enums\\Con"
  },
  {
    "path": "src/ValueObjects/EventResizeInfo.php",
    "chars": 1295,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Enums\\Con"
  },
  {
    "path": "src/ValueObjects/FetchInfo.php",
    "chars": 539,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Carbon\\CarbonImmutable;\n\nuse function Guava\\Calendar\\browser_date_to_"
  },
  {
    "path": "src/ValueObjects/NoEventsClickInfo.php",
    "chars": 636,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nuse Guava\\Calendar\\Contracts\\ContextualInfo;\nuse Guava\\Calendar\\Enums\\Con"
  },
  {
    "path": "src/ValueObjects/ViewDidMountInfo.php",
    "chars": 435,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\ValueObjects;\n\nreadonly class ViewDidMountInfo\n{\n    public CalendarView $view;\n\n    pro"
  },
  {
    "path": "src/helpers.php",
    "chars": 3040,
    "preview": "<?php\n\nnamespace Guava\\Calendar;\n\nuse Carbon\\CarbonImmutable;\nuse Filament\\Support\\Facades\\FilamentTimezone;\n\nif (! func"
  },
  {
    "path": "tests/ArchTest.php",
    "chars": 112,
    "preview": "<?php\n\nit('will not use debugging functions')\n    ->expect(['dd', 'dump', 'ray'])\n    ->each->not->toBeUsed()\n;\n"
  },
  {
    "path": "tests/Pest.php",
    "chars": 1508,
    "preview": "<?php\n\n/*\n|--------------------------------------------------------------------------\n| Test Case\n|---------------------"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 868,
    "preview": "<?php\n\nnamespace Guava\\Calendar\\Tests;\n\nuse Guava\\Calendar\\CalendarServiceProvider;\nuse Illuminate\\Database\\Eloquent\\Fac"
  },
  {
    "path": "tests/Unit/ValueObjects/EventTest.php",
    "chars": 4780,
    "preview": "<?php\n\nuse Carbon\\Carbon;\nuse Guava\\Calendar\\ValueObjects\\CalendarEvent;\nuse Illuminate\\Support\\HtmlString;\n\nbeforeEach("
  },
  {
    "path": "workbench/app/Providers/WorkbenchServiceProvider.php",
    "chars": 409,
    "preview": "<?php\n\nnamespace Workbench\\App\\Providers;\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Illuminate\\Support\\ServiceProvider;"
  }
]

About this extraction

This page contains the full source code of the GuavaCZ/calendar GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 111 files (183.1 KB), approximately 45.1k tokens, and a symbol index with 269 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!