WIP: February 2026 event improvements and calendar refactor #1

Draft
andrew wants to merge 10 commits from feb-2026-event-improvements into master
11 changed files with 617 additions and 27 deletions
Showing only changes of commit 50b9cfe17d - Show all commits

View File

@ -46,6 +46,34 @@ return [
'saved' => 'Your calendar settings have been saved!', 'saved' => 'Your calendar settings have been saved!',
'title' => 'Calendar settings', 'title' => 'Calendar settings',
], ],
'timezone_help' => 'You can override your default time zone here.' 'timezone_help' => 'You can override your default time zone here.',
'event' => [
'when' => 'When',
'all_day' => 'All day',
'location' => 'Location',
'map_coming' => 'Map preview coming soon.',
'no_location' => 'No location set.',
'details' => 'Details',
'repeats' => 'Repeats',
'does_not_repeat' => 'Does not repeat',
'category' => 'Category',
'none' => 'None',
'visibility' => 'Visibility',
'private' => 'Private',
'default' => 'Default',
'all_day_handling' => 'All-day handling',
'timed' => 'Timed',
'all_day_coming' => 'Multi-day all-day UI coming soon',
'alerts' => 'Alerts',
'reminder' => 'Reminder',
'minutes_before' => 'minutes before',
'alerts_coming' => 'No alerts set. (Coming soon)',
'invitees' => 'Invitees',
'invitees_coming' => 'Invitees and RSVP tracking coming soon.',
'attachments' => 'Attachments',
'attachments_coming' => 'Attachment support coming soon.',
'notes' => 'Notes',
'no_description' => 'No description yet.',
],
]; ];

68
lang/it/account.php Normal file
View File

@ -0,0 +1,68 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Account Language Lines
|--------------------------------------------------------------------------
|
| Account, profile, and user settings language lines.
|
*/
// addresses
'address' => [
'city' => 'Citta',
'country' => 'Paese',
'home' => 'Indirizzo di casa',
'label' => 'Etichetta indirizzo',
'line1' => 'Indirizzo riga 1',
'line2' => 'Indirizzo riga 2',
'state' => 'Provincia',
'work' => 'Indirizzo di lavoro',
'zip' => 'CAP',
],
'billing' => [
'home' => 'Usa il tuo indirizzo di casa per la fatturazione',
'work' => 'Usa il tuo indirizzo di lavoro per la fatturazione',
],
'delete' => 'Elimina account',
'delete-your' => 'Elimina il tuo account',
'delete-confirm' => 'Elimina davvero il mio account!',
'email' => 'Email',
'email_address' => 'Indirizzo email',
'first_name' => 'Nome',
'last_name' => 'Cognome',
'phone' => 'Numero di telefono',
'settings' => [
'addresses' => [
'title' => 'Indirizzi',
'subtitle' => 'Gestisci i tuoi indirizzi di casa e lavoro e scegli quale usare per la fatturazione.',
],
'delete' => [
'title' => 'Qui ci sono draghi',
'subtitle' => 'Elimina il tuo account e rimuovi tutte le informazioni dal nostro database. Non puo essere annullato, quindi consigliamo di esportare i tuoi dati prima e migrare a un nuovo provider.',
'explanation' => 'Nota: non e come altre app che "eliminano" i dati&mdash;non stiamo impostando <code>is_deleted = 1</code>, li stiamo rimuovendo dal nostro database.',
],
'delete-confirm' => [
'title' => 'Conferma eliminazione account',
'subtitle' => 'Inserisci la tua password e conferma che vuoi eliminare definitivamente il tuo account.',
],
'information' => [
'title' => 'Informazioni personali',
'subtitle' => 'Il tuo nome, email e altri dettagli principali del account.',
],
'locale' => [
'title' => 'Preferenze locali',
'subtitle' => 'Posizione, fuso orario e altre preferenze regionali per calendari ed eventi.'
],
'password' => [
'title' => 'Password',
'subtitle' => 'Assicurati che il tuo account usi una password lunga e casuale per restare sicuro. Consigliamo anche un password manager!',
],
'title' => 'Impostazioni account',
],
'title' => 'Account',
];

20
lang/it/auth.php Normal file
View File

@ -0,0 +1,20 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'Queste credenziali non corrispondono ai nostri record.',
'password' => 'La password fornita non e corretta.',
'throttle' => 'Troppi tentativi di accesso. Riprova tra :seconds secondi.',
];

79
lang/it/calendar.php Normal file
View File

@ -0,0 +1,79 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Calendar Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used throughout the calendar app,
| including calendar settings and events.
|
*/
'color' => 'Colore',
'create' => 'Crea calendario',
'description' => 'Descrizione',
'ics' => [
'url' => 'URL ICS',
'url_help' => 'Non puoi modificare un URL di calendario pubblico. Se devi fare una modifica, annulla l iscrizione e aggiungilo di nuovo.',
],
'mine' => 'I miei calendari',
'name' => 'Nome calendario',
'settings' => [
'calendar' => [
'title' => 'Impostazioni calendario',
'subtitle' => 'Dettagli e impostazioni per <strong>:calendar</strong>.'
],
'create' => [
'title' => 'Crea un calendario',
'subtitle' => 'Crea un nuovo calendario locale.',
],
'display' => [
'title' => 'Preferenze di visualizzazione',
'subtitle' => 'Regola aspetto e comportamento dei tuoi calendari.'
],
'language_region' => [
'title' => 'Lingua e regione',
'subtitle' => 'Scegli la lingua predefinita, la regione e le preferenze di formattazione. Queste influenzano come date e orari sono mostrati nei calendari e negli eventi.',
],
'my_calendars' => 'Impostazioni per i miei calendari',
'subscribe' => [
'title' => 'Iscriviti a un calendario',
'subtitle' => 'Aggiungi un calendario `.ics` da un altro servizio',
],
'saved' => 'Le impostazioni del calendario sono state salvate!',
'title' => 'Impostazioni calendario',
],
'timezone_help' => 'Puoi sovrascrivere il tuo fuso orario predefinito qui.',
'event' => [
'when' => 'Quando',
'all_day' => 'Tutto il giorno',
'location' => 'Luogo',
'map_coming' => 'Anteprima mappa in arrivo.',
'no_location' => 'Nessun luogo impostato.',
'details' => 'Dettagli',
'repeats' => 'Ripete',
'does_not_repeat' => 'Non si ripete',
'category' => 'Categoria',
'none' => 'Nessuno',
'visibility' => 'Visibilita',
'private' => 'Privato',
'default' => 'Predefinito',
'all_day_handling' => 'Gestione giornata intera',
'timed' => 'Con orario',
'all_day_coming' => 'UI giornate intere multi-giorno in arrivo',
'alerts' => 'Avvisi',
'reminder' => 'Promemoria',
'minutes_before' => 'minuti prima',
'alerts_coming' => 'Nessun avviso impostato. (In arrivo)',
'invitees' => 'Invitati',
'invitees_coming' => 'Invitati e RSVP in arrivo.',
'attachments' => 'Allegati',
'attachments_coming' => 'Supporto allegati in arrivo.',
'notes' => 'Note',
'no_description' => 'Nessuna descrizione.',
],
];

View File

@ -2,10 +2,41 @@
return [ return [
/*
|--------------------------------------------------------------------------
| Common words and phrases
|--------------------------------------------------------------------------
|
| Generic words used throughout the app in more than one location.
|
*/
'address' => 'Indirizzo',
'addresses' => 'Indirizzi',
'calendar' => 'Calendario', 'calendar' => 'Calendario',
'calendars' => 'Calendari', 'calendars' => 'Calendari',
'cancel' => 'Annulla',
'cancel_back' => 'Annulla e torna indietro',
'cancel_funny' => 'Portami via',
'date' => 'Data',
'date_select' => 'Seleziona una data',
'date_format' => 'Formato data',
'date_format_select' => 'Seleziona un formato data',
'event' => 'Evento', 'event' => 'Evento',
'events' => 'Eventi', 'events' => 'Eventi',
'language' => 'Lingua',
'language_select' => 'Seleziona una lingua',
'password' => 'Password',
'region' => 'Regione',
'region_select' => 'Seleziona una regione',
'save_changes' => 'Salva modifiche',
'settings' => 'Impostazioni', 'settings' => 'Impostazioni',
'time' => 'Ora',
'time_select' => 'Seleziona un orario',
'time_format' => 'Formato ora',
'time_format_select' => 'Seleziona un formato ora',
'timezone' => 'Fuso orario',
'timezone_default' => 'Fuso orario predefinito',
'timezone_select' => 'Seleziona un fuso orario',
]; ];

19
lang/it/pagination.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Precedente',
'next' => 'Successivo &raquo;',
];

22
lang/it/passwords.php Normal file
View File

@ -0,0 +1,22 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| outcome such as failure due to an invalid password / reset token.
|
*/
'reset' => 'La tua password e stata reimpostata.',
'sent' => 'Ti abbiamo inviato via email il link per reimpostare la password.',
'throttled' => 'Attendi prima di riprovare.',
'token' => 'Questo token di reimpostazione password non e valido.',
'user' => 'Non troviamo un utente con questo indirizzo email.',
];

198
lang/it/validation.php Normal file
View File

@ -0,0 +1,198 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| as the size rules. Feel free to tweak each of these messages here.
|
*/
'accepted' => 'Il campo :attribute deve essere accettato.',
'accepted_if' => 'Il campo :attribute deve essere accettato quando :other e :value.',
'active_url' => 'Il campo :attribute deve essere un URL valido.',
'after' => 'Il campo :attribute deve essere una data successiva a :date.',
'after_or_equal' => 'Il campo :attribute deve essere una data successiva o uguale a :date.',
'alpha' => 'Il campo :attribute deve contenere solo lettere.',
'alpha_dash' => 'Il campo :attribute deve contenere solo lettere, numeri, trattini e underscore.',
'alpha_num' => 'Il campo :attribute deve contenere solo lettere e numeri.',
'any_of' => 'Il campo :attribute non e valido.',
'array' => 'Il campo :attribute deve essere un array.',
'ascii' => 'Il campo :attribute deve contenere solo caratteri alfanumerici a singolo byte e simboli.',
'before' => 'Il campo :attribute deve essere una data precedente a :date.',
'before_or_equal' => 'Il campo :attribute deve essere una data precedente o uguale a :date.',
'between' => [
'array' => 'Il campo :attribute deve avere tra :min e :max elementi.',
'file' => 'Il campo :attribute deve essere tra :min e :max kilobyte.',
'numeric' => 'Il campo :attribute deve essere tra :min e :max.',
'string' => 'Il campo :attribute deve essere tra :min e :max caratteri.',
],
'boolean' => 'Il campo :attribute deve essere vero o falso.',
'can' => 'Il campo :attribute contiene un valore non autorizzato.',
'confirmed' => 'La conferma del campo :attribute non corrisponde.',
'contains' => 'Il campo :attribute non contiene un valore richiesto.',
'current_password' => 'La password inserita non e corretta.',
'date' => 'Il campo :attribute deve essere una data valida.',
'date_equals' => 'Il campo :attribute deve essere una data uguale a :date.',
'date_format' => 'Il campo :attribute deve corrispondere al formato :format.',
'decimal' => 'Il campo :attribute deve avere :decimal decimali.',
'declined' => 'Il campo :attribute deve essere rifiutato.',
'declined_if' => 'Il campo :attribute deve essere rifiutato quando :other e :value.',
'different' => 'Il campo :attribute e :other devono essere diversi.',
'digits' => 'Il campo :attribute deve essere di :digits cifre.',
'digits_between' => 'Il campo :attribute deve essere tra :min e :max cifre.',
'dimensions' => 'Il campo :attribute ha dimensioni immagine non valide.',
'distinct' => 'Il campo :attribute ha un valore duplicato.',
'doesnt_end_with' => 'Il campo :attribute non deve terminare con uno dei seguenti: :values.',
'doesnt_start_with' => 'Il campo :attribute non deve iniziare con uno dei seguenti: :values.',
'email' => 'Il campo :attribute deve essere un indirizzo email valido.',
'ends_with' => 'Il campo :attribute deve terminare con uno dei seguenti: :values.',
'enum' => 'Il valore selezionato per :attribute non e valido.',
'exists' => 'Il valore selezionato per :attribute non e valido.',
'extensions' => 'Il campo :attribute deve avere una delle seguenti estensioni: :values.',
'file' => 'Il campo :attribute deve essere un file.',
'filled' => 'Il campo :attribute deve avere un valore.',
'gt' => [
'array' => 'Il campo :attribute deve avere piu di :value elementi.',
'file' => 'Il campo :attribute deve essere maggiore di :value kilobyte.',
'numeric' => 'Il campo :attribute deve essere maggiore di :value.',
'string' => 'Il campo :attribute deve essere maggiore di :value caratteri.',
],
'gte' => [
'array' => 'Il campo :attribute deve avere :value elementi o piu.',
'file' => 'Il campo :attribute deve essere maggiore o uguale a :value kilobyte.',
'numeric' => 'Il campo :attribute deve essere maggiore o uguale a :value.',
'string' => 'Il campo :attribute deve essere maggiore o uguale a :value caratteri.',
],
'hex_color' => 'Il campo :attribute deve essere un colore esadecimale valido.',
'image' => 'Il campo :attribute deve essere una immagine.',
'in' => 'Il valore selezionato per :attribute non e valido.',
'in_array' => 'Il campo :attribute deve esistere in :other.',
'in_array_keys' => 'Il campo :attribute deve contenere almeno una delle seguenti chiavi: :values.',
'integer' => 'Il campo :attribute deve essere un numero intero.',
'ip' => 'Il campo :attribute deve essere un indirizzo IP valido.',
'ipv4' => 'Il campo :attribute deve essere un indirizzo IPv4 valido.',
'ipv6' => 'Il campo :attribute deve essere un indirizzo IPv6 valido.',
'json' => 'Il campo :attribute deve essere una stringa JSON valida.',
'list' => 'Il campo :attribute deve essere una lista.',
'lowercase' => 'Il campo :attribute deve essere in minuscolo.',
'lt' => [
'array' => 'Il campo :attribute deve avere meno di :value elementi.',
'file' => 'Il campo :attribute deve essere minore di :value kilobyte.',
'numeric' => 'Il campo :attribute deve essere minore di :value.',
'string' => 'Il campo :attribute deve essere minore di :value caratteri.',
],
'lte' => [
'array' => 'Il campo :attribute non deve avere piu di :value elementi.',
'file' => 'Il campo :attribute deve essere minore o uguale a :value kilobyte.',
'numeric' => 'Il campo :attribute deve essere minore o uguale a :value.',
'string' => 'Il campo :attribute deve essere minore o uguale a :value caratteri.',
],
'mac_address' => 'Il campo :attribute deve essere un indirizzo MAC valido.',
'max' => [
'array' => 'Il campo :attribute non deve avere piu di :max elementi.',
'file' => 'Il campo :attribute non deve essere maggiore di :max kilobyte.',
'numeric' => 'Il campo :attribute non deve essere maggiore di :max.',
'string' => 'Il campo :attribute non deve essere maggiore di :max caratteri.',
],
'max_digits' => 'Il campo :attribute non deve avere piu di :max cifre.',
'mimes' => 'Il campo :attribute deve essere un file di tipo: :values.',
'mimetypes' => 'Il campo :attribute deve essere un file di tipo: :values.',
'min' => [
'array' => 'Il campo :attribute deve avere almeno :min elementi.',
'file' => 'Il campo :attribute deve essere almeno :min kilobyte.',
'numeric' => 'Il campo :attribute deve essere almeno :min.',
'string' => 'Il campo :attribute deve essere almeno :min caratteri.',
],
'min_digits' => 'Il campo :attribute deve avere almeno :min cifre.',
'missing' => 'Il campo :attribute deve essere assente.',
'missing_if' => 'Il campo :attribute deve essere assente quando :other e :value.',
'missing_unless' => 'Il campo :attribute deve essere assente a meno che :other sia :value.',
'missing_with' => 'Il campo :attribute deve essere assente quando :values e presente.',
'missing_with_all' => 'Il campo :attribute deve essere assente quando :values sono presenti.',
'multiple_of' => 'Il campo :attribute deve essere un multiplo di :value.',
'not_in' => 'Il valore selezionato per :attribute non e valido.',
'not_regex' => 'Il formato del campo :attribute non e valido.',
'numeric' => 'Il campo :attribute deve essere un numero.',
'password' => [
'letters' => 'Il campo :attribute deve contenere almeno una lettera.',
'mixed' => 'Il campo :attribute deve contenere almeno una lettera maiuscola e una minuscola.',
'numbers' => 'Il campo :attribute deve contenere almeno un numero.',
'symbols' => 'Il campo :attribute deve contenere almeno un simbolo.',
'uncompromised' => 'Il valore :attribute e apparso in una violazione di dati. Scegli un altro :attribute.',
],
'present' => 'Il campo :attribute deve essere presente.',
'present_if' => 'Il campo :attribute deve essere presente quando :other e :value.',
'present_unless' => 'Il campo :attribute deve essere presente a meno che :other sia :value.',
'present_with' => 'Il campo :attribute deve essere presente quando :values e presente.',
'present_with_all' => 'Il campo :attribute deve essere presente quando :values sono presenti.',
'prohibited' => 'Il campo :attribute e proibito.',
'prohibited_if' => 'Il campo :attribute e proibito quando :other e :value.',
'prohibited_if_accepted' => 'Il campo :attribute e proibito quando :other e accettato.',
'prohibited_if_declined' => 'Il campo :attribute e proibito quando :other e rifiutato.',
'prohibited_unless' => 'Il campo :attribute e proibito a meno che :other sia in :values.',
'prohibits' => 'Il campo :attribute impedisce la presenza di :other.',
'regex' => 'Il formato del campo :attribute non e valido.',
'required' => 'Il campo :attribute e obbligatorio.',
'required_array_keys' => 'Il campo :attribute deve contenere voci per: :values.',
'required_if' => 'Il campo :attribute e obbligatorio quando :other e :value.',
'required_if_accepted' => 'Il campo :attribute e obbligatorio quando :other e accettato.',
'required_if_declined' => 'Il campo :attribute e obbligatorio quando :other e rifiutato.',
'required_unless' => 'Il campo :attribute e obbligatorio a meno che :other sia in :values.',
'required_with' => 'Il campo :attribute e obbligatorio quando :values e presente.',
'required_with_all' => 'Il campo :attribute e obbligatorio quando :values sono presenti.',
'required_without' => 'Il campo :attribute e obbligatorio quando :values non e presente.',
'required_without_all' => 'Il campo :attribute e obbligatorio quando nessuno di :values e presente.',
'same' => 'Il campo :attribute deve corrispondere a :other.',
'size' => [
'array' => 'Il campo :attribute deve contenere :size elementi.',
'file' => 'Il campo :attribute deve essere di :size kilobyte.',
'numeric' => 'Il campo :attribute deve essere :size.',
'string' => 'Il campo :attribute deve essere di :size caratteri.',
],
'starts_with' => 'Il campo :attribute deve iniziare con uno dei seguenti: :values.',
'string' => 'Il campo :attribute deve essere una stringa.',
'timezone' => 'Il campo :attribute deve essere un fuso orario valido.',
'unique' => 'Il valore :attribute e gia stato preso.',
'uploaded' => 'Il campo :attribute non e riuscito a caricare.',
'uppercase' => 'Il campo :attribute deve essere in maiuscolo.',
'url' => 'Il campo :attribute deve essere un URL valido.',
'ulid' => 'Il campo :attribute deve essere un ULID valido.',
'uuid' => 'Il campo :attribute deve essere un UUID valido.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute placeholder
| with something more reader friendly such as "E-Mail Address" instead
| of "email". This simply helps us make our message more expressive.
|
*/
'attributes' => [],
];

View File

@ -213,6 +213,7 @@ main {
@apply fixed right-0 top-2 flex flex-col bg-gray-100 gap-6 p-6 rounded-l-xl; @apply fixed right-0 top-2 flex flex-col bg-gray-100 gap-6 p-6 rounded-l-xl;
height: calc(100dvh - 0.5rem); height: calc(100dvh - 0.5rem);
width: 33dvw; width: 33dvw;
display: none;
} }
} }
@ -260,7 +261,7 @@ main {
header { header {
menu { menu {
@apply relative top-auto right-auto h-auto w-auto rounded-none bg-transparent; @apply relative top-auto right-auto h-auto w-auto rounded-none bg-transparent;
@apply flex flex-row items-center justify-end gap-4; @apply flex flex-row items-center justify-end gap-4 p-0;
} }
} }
} }

View File

@ -3,26 +3,29 @@
} }
dialog { dialog {
@apply grid fixed top-0 right-0 bottom-0 left-0 m-0 p-0 pointer-events-none; @apply grid fixed inset-0 m-0 p-0 pointer-events-none;
@apply justify-items-center items-start bg-transparent opacity-0 invisible; @apply place-items-center bg-transparent opacity-0 invisible;
@apply w-full h-full max-w-full max-h-full overflow-y-hidden; @apply w-full h-full max-w-none max-h-none overflow-clip;
background-color: rgba(26, 26, 26, 0.75); background-color: rgba(26, 26, 26, 0.75);
backdrop-filter: blur(0.25rem); backdrop-filter: blur(0.25rem);
grid-template-rows: minmax(20dvh, 2rem) 1fr; /*(grid-template-rows: minmax(20dvh, 2rem) 1fr; */
overscroll-behavior: contain;
scrollbar-gutter: auto;
transition: transition:
background-color 150ms cubic-bezier(0,0,.2,1),
opacity 150ms cubic-bezier(0,0,.2,1), opacity 150ms cubic-bezier(0,0,.2,1),
visibility 150ms cubic-bezier(0,0,.2,1); visibility 150ms cubic-bezier(0,0,.2,1);
z-index: 100; z-index: 100;
#modal { #modal {
@apply relative rounded-lg bg-white border-gray-200 p-0; @apply relative rounded-xl bg-white border-gray-200 p-0;
@apply flex flex-col items-start col-start-1 row-start-2 translate-y-4; @apply flex flex-col items-start col-start-1 translate-y-4;
@apply overscroll-contain overflow-y-auto; @apply overscroll-contain overflow-y-auto;
max-height: calc(100vh - 5em); max-height: calc(100vh - 5em);
width: 91.666667%; width: 91.666667%;
max-width: 36rem; max-width: 36rem;
transition: all 150ms cubic-bezier(0,0,.2,1); transition: all 150ms cubic-bezier(0,0,.2,1);
box-shadow: #00000040 0 1.5rem 4rem -0.5rem; box-shadow: 0 1.5rem 4rem -0.5rem rgba(0, 0, 0, 0.4);
> .close-modal { > .close-modal {
@apply block absolute top-4 right-4; @apply block absolute top-4 right-4;

View File

@ -1,26 +1,147 @@
@php
$meta = $event->meta;
$title = $meta->title ?? '(no title)';
$allDay = (bool) ($meta->all_day ?? false);
$calendarName = $calendar->displayname ?? __('common.calendar');
$calendarColor = $calendar->meta_color ?? $calendar->calendarcolor ?? default_calendar_color();
$rrule = $meta?->extra['rrule'] ?? null;
$tzid = $meta?->extra['tzid'] ?? $tz;
$locationLabel = $meta?->location_label ?? '';
$hasLocation = trim((string) $locationLabel) !== '';
$venue = $meta?->venue;
$addressLine1 = $venue?->street;
$addressLine2 = trim(implode(', ', array_filter([
$venue?->city,
$venue?->state,
$venue?->postal,
])));
$addressLine3 = $venue?->country;
@endphp
<x-modal.content> <x-modal.content>
<x-modal.title> <x-modal.title>
{{ $event->meta->title ?? '(no title)' }} <div class="flex items-center gap-3">
<span class="inline-block h-3 w-3 rounded-full" style="background: {{ $calendarColor }};"></span>
<span>{{ $title }}</span>
</div>
</x-modal.title> </x-modal.title>
<x-modal.body> <x-modal.body>
<p class="text-gray-700"> <div class="flex flex-col gap-6">
{{ $start->format('l, F j, Y · g:i A') }} <section class="space-y-1">
@unless ($start->equalTo($end)) <p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.when') }}</p>
&nbsp;&nbsp; @if ($allDay)
{{ $end->isSameDay($start) <p class="text-lg text-gray-900">
? $end->format('g:i A') {{ $start->format('l, F j, Y') }}
: $end->format('l, F j, Y · g:i A') }} @unless ($start->isSameDay($end))
@endunless &nbsp;&nbsp;
</p> {{ $end->format('l, F j, Y') }}
@endunless
<span class="text-sm text-gray-500">({{ __('calendar.event.all_day') }})</span>
</p>
@else
<p class="text-lg text-gray-900">
{{ $start->format('l, F j, Y · g:i A') }}
@unless ($start->equalTo($end))
&nbsp;&nbsp;
{{ $end->isSameDay($start)
? $end->format('g:i A')
: $end->format('l, F j, Y · g:i A') }}
@endunless
</p>
@endif
<p class="text-sm text-gray-500">{{ __('common.timezone') }}: {{ $tzid }}</p>
</section>
@if ($event->meta->location) <section class="space-y-1">
<p><strong>Where:</strong> {{ $event->meta->location_label }}</p> <p class="text-xs uppercase tracking-wide text-gray-400">{{ __('common.calendar') }}</p>
@endif <p class="text-gray-900">{{ $calendarName }}</p>
</section>
@if ($event->meta->description) <section class="space-y-2">
<p> <p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.location') }}</p>
{!! Str::markdown(nl2br(e($event->meta->description))) !!} @if ($hasLocation)
</p> <p class="text-gray-900">{{ $locationLabel }}</p>
@endif @if ($addressLine1 || $addressLine2 || $addressLine3)
<div class="text-sm text-gray-600">
@if ($addressLine1)
<div>{{ $addressLine1 }}</div>
@endif
@if ($addressLine2)
<div>{{ $addressLine2 }}</div>
@endif
@if ($addressLine3)
<div>{{ $addressLine3 }}</div>
@endif
</div>
@endif
<div class="mt-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-4 text-sm text-gray-500">
{{ __('calendar.event.map_coming') }}
</div>
@else
<p class="text-sm text-gray-500">{{ __('calendar.event.no_location') }}</p>
@endif
</section>
<section class="space-y-2">
<p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.details') }}</p>
<div class="grid grid-cols-1 gap-3 text-sm text-gray-700">
<div>
<span class="text-gray-500">{{ __('calendar.event.repeats') }}:</span>
@if ($rrule)
<span class="ml-1 font-mono text-gray-800">{{ $rrule }}</span>
@else
<span class="ml-1 text-gray-500">{{ __('calendar.event.does_not_repeat') }}</span>
@endif
</div>
<div>
<span class="text-gray-500">{{ __('calendar.event.category') }}:</span>
<span class="ml-1">{{ $meta->category ?? __('calendar.event.none') }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('calendar.event.visibility') }}:</span>
<span class="ml-1">{{ ($meta->is_private ?? false) ? __('calendar.event.private') : __('calendar.event.default') }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('calendar.event.all_day_handling') }}:</span>
<span class="ml-1">
{{ $allDay ? __('calendar.event.all_day') : __('calendar.event.timed') }}
<span class="text-gray-400">· {{ __('calendar.event.all_day_coming') }}</span>
</span>
</div>
</div>
</section>
<section class="space-y-2">
<p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.alerts') }}</p>
@if (!is_null($meta->reminder_minutes))
<p class="text-sm text-gray-700">
{{ __('calendar.event.reminder') }}: {{ $meta->reminder_minutes }} {{ __('calendar.event.minutes_before') }}
</p>
@else
<p class="text-sm text-gray-500">{{ __('calendar.event.alerts_coming') }}</p>
@endif
</section>
<section class="space-y-2">
<p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.invitees') }}</p>
<p class="text-sm text-gray-500">{{ __('calendar.event.invitees_coming') }}</p>
</section>
<section class="space-y-2">
<p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.attachments') }}</p>
<p class="text-sm text-gray-500">{{ __('calendar.event.attachments_coming') }}</p>
</section>
<section class="space-y-2">
<p class="text-xs uppercase tracking-wide text-gray-400">{{ __('calendar.event.notes') }}</p>
@if ($meta->description)
<div class="prose prose-sm max-w-none text-gray-800">
{!! Str::markdown(nl2br(e($meta->description))) !!}
</div>
@else
<p class="text-sm text-gray-500">{{ __('calendar.event.no_description') }}</p>
@endif
</section>
</div>
</x-modal.body> </x-modal.body>
</x-modal.content> </x-modal.content>