Adds locations table for additional location meta data; updates seeder to add locations; separates modal out into parts

This commit is contained in:
Andrew Gioia 2025-07-30 10:57:26 -04:00
parent c58a498e44
commit 9f3ceabd7d
Signed by: andrew
GPG Key ID: FC09694A000800C8
7 changed files with 146 additions and 24 deletions

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('locations', function (Blueprint $table) {
$table->id(); // BIGINT PK
$table->string('display_name'); // “Home”, “Reading Terminal Market”
$table->text('raw_address')->nullable(); // what the user originally typed
$table->string('street')->nullable();
$table->string('city')->nullable();
$table->string('state', 64)->nullable();
$table->string('postal', 32)->nullable();
$table->string('country', 64)->nullable();
$table->decimal('lat', 9, 6)->nullable(); // ±90.000000
$table->decimal('lon', 9, 6)->nullable(); // ±180.000000
$table->string('phone', 32)->nullable();
$table->json('hours_json')->nullable(); // flexible opening-hours blob
$table->timestamps();
// optional composite for fast “near me” queries
$table->index(['lat', 'lon'], 'idx_locations_lat_lon');
});
}
public function down(): void
{
Schema::dropIfExists('locations');
}
};

View File

@ -9,23 +9,37 @@ return new class extends Migration
public function up(): void
{
Schema::create('event_meta', function (Blueprint $table) {
// FK = PK → calendarobjects.id
$table->unsignedInteger('event_id')->primary();
/* cached fields from ical blob for quick ui */
// cached fields from the VEVENT
$table->string('title')->nullable();
$table->text('description')->nullable();
// free-text fallback if no geocoded location
$table->string('location')->nullable();
// foreign-key to the richer locations table
$table->unsignedBigInteger('location_id')->nullable();
$table->foreign('location_id')
->references('id')
->on('locations')
->nullOnDelete();
// all-day flag
$table->boolean('all_day')->default(false);
/* extra metadata */
// extra metadata
$table->string('category')->nullable();
$table->string('reminder_minutes')->nullable();
$table->boolean('is_private')->default(false);
$table->dateTime('start_at')->nullable();
$table->dateTime('end_at')->nullable();
$table->json('extra')->nullable();
$table->timestamps();
// FK to Sabre calendarobjects table
$table->foreign('event_id')
->references('id')
->on('calendarobjects')

View File

@ -71,10 +71,67 @@ class DatabaseSeeder extends Seeder
/**
*
* create helper function for events to be added
* create the locations connected to each event
**/
$insertEvent = function (Carbon $start, string $summary) use ($calId) {
// locations
$locationSeeds = [
'Home' => [ // free-text, no geo
'display' => 'Home',
'raw' => null,
],
'Living Room' => [
'display' => 'Living Room',
'raw' => null,
],
'McCahill Park' => [
'display' => 'McCahill Park',
'raw' => '625 Hemlock Hollow Rd, Pittsburgh, PA 15238',
],
'Meadow Park' => [
'display' => 'Meadow Park',
'raw' => '2 Meadow Park Lane, Pittsburgh, PA 15215',
],
'AHN Pediatrics' => [
'display' => 'AHN Pediatrics',
'raw' => '3394 Saxonburg Blvd Suite 600, Glenshaw, PA 15116',
],
'The Discovery School' => [
'display' => 'The Discovery School',
'raw' => '4225 Middle Rd, Allison Park, PA 15101',
],
'Fairview Elementary School' => [
'display' => 'Fairview Elementary School',
'raw' => '738 Dorseyville Rd, Pittsburgh, PA 15238',
],
];
// create the locations first
$locationIdMap = [];
foreach ($locationSeeds as $key => $data) {
$locId = DB::table('locations')->updateOrInsert(
['display_name' => $data['display']], // uniqueness key
[
'raw_address' => $data['raw'],
'created_at' => now(),
'updated_at' => now(),
]
);
// updateOrInsert returns boolean; fetch id explicitly
$locId = DB::table('locations')->where('display_name', $data['display'])->value('id');
$locationIdMap[$key] = $locId;
}
/**
*
* cevent creation helper function
**/
$insertEvent = function (Carbon $start,
string $summary,
string $locationKey) use ($calId, $locationIdMap)
{
// set base vars
$uid = Str::uuid().'@kithkin.lan';
@ -85,6 +142,10 @@ class DatabaseSeeder extends Seeder
$dtstart = $start->copy()->utc()->format('Ymd\\THis\\Z');
$dtend = $end->copy()->utc()->format('Ymd\\THis\\Z');
$locationDisplay = $locationKey;
$locationRaw = $locationSeeds[$locationKey]['raw'] ?? null;
$icalLocation = $locationRaw ?? $locationDisplay;
$ical = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
@ -117,7 +178,8 @@ ICS;
[
'title' => $summary,
'description' => 'Automatically seeded event',
'location' => 'Home Office',
'location' => $locationRaw ? null : $locationDisplay,
'location_id' => $locationIdMap[$locationKey] ?? null,
'all_day' => false,
'category' => 'Demo',
'start_at' => $start->copy()->utc(),
@ -136,23 +198,23 @@ ICS;
$now = Carbon::now()->setSeconds(0);
// 3 events today
$insertEvent($now->copy(), 'Playground with James');
$insertEvent($now->copy()->addHours(2), 'Lunch with Daniel');
$insertEvent($now->copy()->addHours(4), 'Baseball practice');
$insertEvent($now->copy(), 'Playground with James', 'McCaHill Park');
$insertEvent($now->copy()->addHours(2), 'Lunch with Daniel', 'Home');
$insertEvent($now->copy()->addHours(4), 'Baseball practice', 'Meadow Park');
// 1 event 3 days ago
$past = $now->copy()->subDays(3)->setTime(10, 0);
$insertEvent($past, 'Kids doctors appointments');
$insertEvent($past, 'Kids doctors appointments', 'AHN Pediatrics');
// 1 event 2 days ahead
$future2 = $now->copy()->addDays(2)->setTime(14, 0);
$insertEvent($future2, 'Teacher conference (Nuthatches)');
$insertEvent($future2, 'Teacher conference (Nuthatches)', 'The Discovery School');
// 2 events 5 days ahead
$future5a = $now->copy()->addDays(5)->setTime(9, 0);
$future5b = $future5a->copy()->addHours(2);
$insertEvent($future5a, 'Teacher conference (3rd grade)');
$insertEvent($future5b, 'Family game night');
$insertEvent($future5a, 'Teacher conference (3rd grade)', 'Fairview Elementary');
$insertEvent($future5b, 'Family game night', 'Living Room');
/**
*

Binary file not shown.

View File

@ -0,0 +1,3 @@
<section class="flex flex-col px-8 pb-6">
{{ $slot }}
</section>

View File

@ -0,0 +1,9 @@
@props([
'border' => false,
])
<header @class(['px-8 py-6', 'header--with-border' => $border])>
<h2 class="text-lg font-semibold">
{{ $slot }}
</h2>
</header>

View File

@ -1,10 +1,9 @@
<x-modal.content>
<div class="p-6 space-y-4">
<h2 class="text-lg font-semibold">
{{ $event->meta->title ?? '(no title)' }}
</h2>
<p class="text-sm text-gray-600">
<x-modal.title>
{{ $event->meta->title ?? '(no title)' }}
</x-modal.title>
<x-modal.body>
<p class="text-gray-700">
{{ $start->format('l, F j, Y · g:i A') }}
@unless ($start->equalTo($end))
&nbsp;&nbsp;
@ -15,13 +14,13 @@
</p>
@if ($event->meta->location)
<p class="text-sm"><strong>Where:</strong> {{ $event->meta->location }}</p>
<p><strong>Where:</strong> {{ $event->meta->location }}</p>
@endif
@if ($event->meta->description)
<div class="prose max-w-none text-sm">
{!! nl2br(e($event->meta->description)) !!}
</div>
<div>
{!! nl2br(e($event->meta->description)) !!}
</div>
@endif
</div>
</x-modal.body>
</x-modal.content>