Skip to content

feat: talks time#105

Merged
danielhe4rt merged 6 commits into
3.xfrom
feat/event-talks
Nov 18, 2025
Merged

feat: talks time#105
danielhe4rt merged 6 commits into
3.xfrom
feat/event-talks

Conversation

@RichardGL11
Copy link
Copy Markdown
Collaborator

@RichardGL11 RichardGL11 commented Nov 17, 2025

Summary by CodeRabbit

  • New Features

    • Talks can be scheduled with explicit start/end times and form fields now collect those times.
    • Form layout improved for talk proposals; scheduling fields respect event boundaries.
    • Added "Rejected" talk status with updated visuals.
    • Automatic validation prevents scheduling conflicts.
  • Tests

    • Added tests covering talk scheduling and conflict detection scenarios.
  • Chores

    • New seeders added and seeding workflow simplified.

@danielhe4rt
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 18, 2025

Walkthrough

Adds talk scheduling (starts_at, ends_at) across database, models, factories, forms, and tests; introduces schedule availability validation and a Rejected talk status; restructures Filament talk forms with a reusable StartEndFieldsSchema; and refactors seeders into BaseSeeder and ThreeDotsSeeder.

Changes

Cohort / File(s) Summary
Factory & Migration
app-modules/events/database/factories/EventFactory.php, app-modules/events/database/factories/TalkFactory.php, app-modules/events/database/migrations/2025_11_05_192008_create_events_talks_table.php
EventFactory defaults changed to use Date::today() / ->endOfDay(); TalkFactory adds starts_at and ends_at; migration adds starts_at and ends_at timestamp columns to events_talks.
Enum
app-modules/events/src/Enums/Talks/TalkStatusEnum.php
Adds Rejected = 'rejected'; maps Rejected to red color and XCircle icon; updates labels (Accepted label adjusted).
Filament Form Layouts
app-modules/events/src/Filament/Admin/Resources/Talks/Schemas/TalkForm.php, app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php
Replaces flat fields with Flex-based grouped Sections; integrates StartEndFieldsSchema for scheduling; adjusts field spans, live/preload behavior, and labels.
Reusable Schema
app-modules/events/src/Filament/Shared/Schemas/StartEndFieldsSchema.php
New class exposing make() returning starts_at and ends_at DateTimePickers with dynamic min/max from related event and conditional validation rule binding to AvailableTalkSchedule.
Models & Scopes
app-modules/events/src/Models/EventModel.php, app-modules/events/src/Models/Talk.php
EventModel: adds talks() HasMany, speakers() HasManyThrough, and availableHours() scope to filter by status/time; Talk model: adds starts_at and ends_at to $fillable and casts.
Validation Rule
app/Rules/AvailableTalkSchedule.php
New ValidationRule that checks proposed talk time window against EventModel::availableHours() and fails with a Portuguese error message when conflict exists.
Tests
app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php, app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php
Tests updated to submit starts_at/ends_at; new scenarios assert rejection on overlap with Accepted talks and acceptance when overlapping talks are Pending.
Seeders
database/seeders/BaseSeeder.php, database/seeders/DatabaseSeeder.php, database/seeders/ThreeDotsSeeder.php
Adds BaseSeeder and ThreeDotsSeeder; DatabaseSeeder delegates to them. BaseSeeder seeds admin, tenant, events; ThreeDotsSeeder seeds a "3 Pontos" tenant/event and associated talks.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Form
    participant Form as TalkForm
    participant Rule as AvailableTalkSchedule
    participant Event as EventModel
    participant DB as Database

    User->>Form: Submit talk (event_id, starts_at, ends_at, ...)
    Form->>Rule: Invoke AvailableTalkSchedule(event_id, starts_at) with ends_at
    Rule->>Event: availableHours(event_id, start, end)
    Event->>DB: Query talks with status Accepted/Done overlapping window
    alt No conflict
        DB-->>Event: none found
        Event-->>Rule: available
        Rule-->>Form: passes
        Form-->>DB: Persist talk
        DB-->>Form: saved
        Form-->>User: success
    else Conflict found
        DB-->>Event: conflicting talk
        Event-->>Rule: not available
        Rule-->>Form: fail (Portuguese message)
        Form-->>User: validation error on ends_at
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay special attention to: AvailableTalkSchedule logic and message, EventModel.availableHours() overlap calculations and status filtering, Form/StartEndFieldsSchema dynamic min/max behavior, and seeder relationship chaining.

Poem

🐰 I hopped through code with careful paws,

starts_at, ends_at now set the laws.
When times collide a rule will say “no”,
Rejected hops red, and forms will flow.
Seeders plant fields where new events grow.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'feat: talks time' is vague and generic, using minimal description that lacks specificity about what talk timing functionality is being added. Replace with a more descriptive title such as 'feat: add scheduling and validation for event talks' or 'feat: implement talk time slots with availability checking' to clarify the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/event-talks

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e27ec01 and 13ebb71.

📒 Files selected for processing (2)
  • app-modules/events/src/Models/EventModel.php (3 hunks)
  • database/seeders/ThreeDotsSeeder.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • database/seeders/ThreeDotsSeeder.php
🧰 Additional context used
🧬 Code graph analysis (1)
app-modules/events/src/Models/EventModel.php (2)
app-modules/tenant/src/Models/Tenant.php (1)
  • Tenant (22-94)
app-modules/events/src/Models/Talk.php (1)
  • UseFactory (16-67)
🔇 Additional comments (3)
app-modules/events/src/Models/EventModel.php (3)

12-12: LGTM! Clean imports and documentation.

The necessary imports for the new relationships (HasMany, HasManyThrough), scope (Builder), and enum (TalkStatusEnum) are properly added. The docblock update for $start_at improves IDE support.

Also applies to: 18-18, 23-24, 39-39


143-149: LGTM! Correctly defined relationship.

The talks() HasMany relationship properly establishes the Event-to-Talk connection with the correct foreign key.


159-172: LGTM! Availability logic is correct.

The availableHours scope properly filters events for talk scheduling:

  • Lines 162-163: Ensures the event timeframe encompasses the requested slot
  • Line 167: whereIn cleanly filters for both Accepted and Done statuses (resolves the SQL grouping issue mentioned in past comments)
  • Lines 169-170: Standard interval overlap detection is correct

The whereDoesntHave + whereIn approach avoids the SQL logic errors that would occur with ungrouped orWhere clauses.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

@danielhe4rt
Copy link
Copy Markdown
Contributor

@coderabbitai generate improvements

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app-modules/events/database/migrations/2025_11_05_192008_create_events_talks_table.php (1)

36-39: Implement a proper rollback for events_talks

down() currently doesn’t drop the events_talks table and still contains a commented reference to events_c4p, so rolling back this migration is effectively impossible.

A straightforward fix:

     public function down(): void
     {
-        //        Schema::dropIfExists('events_c4p');
+        Schema::dropIfExists('events_talks');
     }
🧹 Nitpick comments (5)
database/seeders/ThreeDotsSeeder.php (1)

22-31: Make admin user dependency explicit or self-contained

This seeder assumes an admin user with name = 'admin' exists; if run standalone (without BaseSeeder) $user will be null and ->for($user, 'owner') will fail.

Consider either:

  • Guarding for a missing admin and creating/fetching via firstOrCreate on a stable key (e.g. email/username), or
  • Documenting that this seeder must always be invoked after BaseSeeder and tightening the lookup to match how BaseSeeder creates the user (e.g. by username or email).
database/seeders/BaseSeeder.php (1)

25-62: Seeder is non-idempotent and may break on repeated runs

The hard-coded admin user and tenant (he4rt) are created unconditionally; on a DB with existing data or on repeated db:seed runs this can easily hit unique constraints on username/email/slug.

If you expect db:seed to be safe to re-run, consider switching to firstOrCreate/updateOrCreate for the admin user and tenant (and adjusting dependent records accordingly). Otherwise, a short comment noting that this seeder assumes a fresh database would help future maintainers.

app-modules/events/database/migrations/2025_11_05_192008_create_events_talks_table.php (1)

27-28: Confirm whether scheduling fields must always be present

starts_at and ends_at are non-nullable timestamps. If there are flows where a talk exists before it’s scheduled (e.g., proposal phase), inserts will fail unless those fields are populated.

If unscheduled talks are valid in your domain, consider marking these columns as nullable and enforcing presence at the application/validation level instead.

app-modules/events/src/Filament/Shared/Schemas/StartEndFieldsSchema.php (2)

21-30: Consider caching the event lookup to avoid repeated queries.

The callback fetches the event from the database each time it's invoked. If this callback is triggered multiple times during form rendering or validation, it could result in unnecessary queries.

Additionally, since start_at is cast to datetime in EventModel (line 186), it should already be a Carbon instance, making Date::parse() unnecessary.

Consider one of these approaches:

Option 1: Remove unnecessary Date::parse()

-                    return Date::parse($event->start_at);
+                    return $event->start_at;

Option 2: Cache the event lookup (if the callback is invoked frequently)

->minDate(function (Get $get): ?Carbon {
    static $eventCache = [];
    $eventId = $get('event_id');
    
    if (!isset($eventCache[$eventId])) {
        $eventCache[$eventId] = EventModel::query()->find($eventId);
    }
    
    return $eventCache[$eventId]?->start_at;
})

33-43: Remove unnecessary Date::parse() and consider caching.

Same concerns as the starts_at field: Date::parse() is unnecessary since end_at is cast to datetime, and repeated database queries could be inefficient.

Apply this diff:

-                    return Date::parse($event->end_at);
+                    return $event->end_at;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ced24bf and e27ec01.

📒 Files selected for processing (15)
  • app-modules/events/database/factories/EventFactory.php (1 hunks)
  • app-modules/events/database/factories/TalkFactory.php (1 hunks)
  • app-modules/events/database/migrations/2025_11_05_192008_create_events_talks_table.php (1 hunks)
  • app-modules/events/src/Enums/Talks/TalkStatusEnum.php (4 hunks)
  • app-modules/events/src/Filament/Admin/Resources/Talks/Schemas/TalkForm.php (1 hunks)
  • app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php (2 hunks)
  • app-modules/events/src/Filament/Shared/Schemas/StartEndFieldsSchema.php (1 hunks)
  • app-modules/events/src/Models/EventModel.php (3 hunks)
  • app-modules/events/src/Models/Talk.php (2 hunks)
  • app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php (2 hunks)
  • app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php (3 hunks)
  • app/Rules/AvailableTalkSchedule.php (1 hunks)
  • database/seeders/BaseSeeder.php (1 hunks)
  • database/seeders/DatabaseSeeder.php (1 hunks)
  • database/seeders/ThreeDotsSeeder.php (1 hunks)
🧰 Additional context used
🪛 PHPMD (2.15.0)
app/Rules/AvailableTalkSchedule.php

25-25: Avoid unused parameters such as '$attribute'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (12)
app-modules/events/src/Enums/Talks/TalkStatusEnum.php (1)

15-47: Enum extension and mappings look consistent

The new Rejected status is correctly added to the enum and fully wired into getColor, getIcon, and getLabel, and the Accepted label change is coherent with the naming. No issues spotted here.

database/seeders/DatabaseSeeder.php (1)

15-20: Seeder delegation is clear and ordered

Delegating to BaseSeeder and ThreeDotsSeeder via $this->call([...]) simplifies DatabaseSeeder and makes the seeding order explicit. Looks good.

app-modules/events/tests/Feature/Filament/Admin/Talk/CreateTalk.php (1)

39-40: LGTM!

The test properly includes scheduling fields with a valid 1-hour duration between starts_at and ends_at.

app-modules/events/src/Models/Talk.php (1)

31-32: LGTM!

The starts_at and ends_at fields are correctly added to the fillable array and cast as datetime, enabling proper mass assignment and type handling for talk scheduling.

Also applies to: 63-64

app-modules/events/database/factories/EventFactory.php (1)

32-34: LGTM!

The changes improve semantic clarity by setting event_at to start-of-day and end_at to end-of-day, creating more realistic test data for full-day events.

app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php (2)

40-41: LGTM!

Test properly includes scheduling fields with a valid duration.


73-117: Well-structured validation tests for talk scheduling.

The test suite effectively covers the availability validation logic, ensuring that overlapping talks are rejected when status is Accepted, but allowed when status is Pending.

app-modules/events/src/Filament/Admin/Resources/Talks/Schemas/TalkForm.php (1)

22-71: LGTM! Well-structured form refactoring.

The new Flex-based layout with logical sections ("Proposta da Palestra", "Horários da Palestra", "Detalhes e Conteúdo") significantly improves organization and user experience. The use of StartEndFieldsSchema promotes code reuse and consistency.

app-modules/events/src/Filament/App/Talks/Schemas/TalkForm.php (1)

25-84: LGTM! Consistent form structure with proper tenant scoping.

The refactored layout mirrors the Admin form while appropriately:

  • Filtering events by tenant (line 37)
  • Auto-setting user_id, tenant_id, and status via hidden fields
  • Defaulting status to Pending for user-submitted talks

The use of StartEndFieldsSchema maintains consistency across the application.

app-modules/events/src/Filament/Shared/Schemas/StartEndFieldsSchema.php (1)

45-61: Validation logic looks good.

The conditional application of AvailableTalkSchedule is correct—it only validates when both event_id and starts_at are present. The type casting on line 54 should work, though DateTimePicker typically provides properly formatted datetime values already.

app-modules/events/src/Models/EventModel.php (2)

146-149: LGTM!

The talks() relationship is correctly defined.


162-163: Event time boundary checks are correct.

The logic correctly ensures the event's start_at is before or equal to the requested start time, and end_at is after or equal to the requested end time, confirming the event spans the entire requested window.

Comment thread app-modules/events/database/factories/TalkFactory.php
Comment thread app-modules/events/src/Models/EventModel.php
Comment thread app-modules/events/src/Models/EventModel.php
Comment thread app/Rules/AvailableTalkSchedule.php
Comment thread app/Rules/AvailableTalkSchedule.php
Comment thread database/seeders/ThreeDotsSeeder.php Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 18, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the PR details and diff
gh pr view 105 --json files,additions,deletions,title,body

# Get the full diff of the PR
gh pr diff 105

Length of output: 31314


@danielhe4rt Great work on implementing the talk scheduling feature! I've analyzed the code and identified several improvements across bugs, performance, validation, and testing. Here are my recommendations:

🐛 Critical Bugs

1. Error Message Parameters Reversed

File: app/Rules/AvailableTalkSchedule.php (Line 36)

// Current (incorrect)
$fail(sprintf('O Horário %s até %s não está disponível.', $end, $start));

// Should be
$fail(sprintf('O Horário %s até %s não está disponível.', $start, $end));

2. Logic Error in Conflict Detection Query

File: app-modules/events/src/Models/EventModel.php (Lines 165-168)

The orWhere creates incorrect logic - it should be checking if the talk is (Accepted OR Done) AND overlapping:

// Current (incorrect)
$talkQuery
    ->where('status', '=', TalkStatusEnum::Accepted->value)
    ->orWhere('status', '=', TalkStatusEnum::Done->value);

// Should be
$talkQuery->whereIn('status', [
    TalkStatusEnum::Accepted->value,
    TalkStatusEnum::Done->value,
]);

3. Incorrect Relationship Definition

File: app-modules/events/src/Models/EventModel.php (Lines 152-156)

// Current (incorrect) - uses 'user_id' as first foreign key
public function speakers(): HasManyThrough
{
    return $this->hasManyThrough(User::class, Talk::class, 'user_id');
}

// Should be
public function speakers(): HasManyThrough
{
    return $this->hasManyThrough(
        User::class,
        Talk::class,
        'event_id',  // Foreign key on talks table
        'id',        // Foreign key on users table
        'id',        // Local key on events table
        'user_id'    // Local key on talks table
    );
}

⚡ Performance Issues

4. N+1 Query Problem in Form Schema

File: app-modules/events/src/Filament/Shared/Schemas/StartEndFieldsSchema.php

The event is fetched twice (once for minDate, once for maxDate):

public static function make(): array
{
    return [
        DateTimePicker::make('starts_at')
            ->label('Starts at')
            ->minDate(function (Get $get): ?Carbon {
                $event = self::getEvent($get);
                return $event ? Date::parse($event->start_at) : null;
            })
            ->maxDate(function (Get $get): ?Carbon {
                $event = self::getEvent($get);
                return $event ? Date::parse($event->end_at) : null;
            })
            ->required(),
        DateTimePicker::make('ends_at')
            ->minDate(function (Get $get): ?Carbon {
                $event = self::getEvent($get);
                return $event ? Date::parse($event->start_at) : null;
            })
            ->maxDate(function (Get $get): ?Carbon {
                $event = self::getEvent($get);
                return $event ? Date::parse($event->end_at) : null;
            })
            ->after('starts_at')
            ->rules([/* ... */])
            ->label('Ends at')
            ->required(),
    ];
}

// Add helper method with memoization
private static function getEvent(Get $get): ?EventModel
{
    static $cache = [];
    $eventId = $get('event_id');
    
    if (!$eventId) {
        return null;
    }
    
    if (!isset($cache[$eventId])) {
        $cache[$eventId] = EventModel::query()
            ->where('id', $eventId)
            ->first(['id', 'start_at', 'end_at']);
    }
    
    return $cache[$eventId];
}

5. Missing Database Indexes

File: app-modules/events/database/migrations/2025_11_05_192008_create_events_talks_table.php

Add indexes for the new timestamp columns:

public function up(): void
{
    Schema::create('events_talks', function (Blueprint $table): void {
        // ... existing columns ...
        $table->timestamp('starts_at');
        $table->timestamp('ends_at');
        $table->timestamps();
        
        // Add indexes for performance
        $table->index(['event_id', 'starts_at', 'ends_at']);
        $table->index('status');
    });
}

🔒 Validation Issues

6. Edit Scenario Not Handled

File: app/Rules/AvailableTalkSchedule.php

The validation will block editing existing talks because it doesn't exclude the current record:

class AvailableTalkSchedule implements ValidationRule
{
    public function __construct(
        private readonly string|int $eventId,
        private readonly string $start_at,
        private readonly ?int $excludeTalkId = null,  // Add this
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $start = Date::parse($this->start_at)->toDateTimeString();
        $end = Date::parse($value)->toDateTimeString();

        $query = EventModel::query()
            ->where('id', $this->eventId)
            ->availableHours(start: $start, end: $end);
        
        // Exclude current talk when editing
        if ($this->excludeTalkId) {
            $query->whereDoesntHave('talks', function (Builder $q) {
                $q->where('id', $this->excludeTalkId);
            });
        }

        if (!$query->exists()) {
            $fail(sprintf('O Horário %s até %s não está disponível.', $start, $end));
        }
    }
}

Then update the usage in StartEndFieldsSchema.php:

->rules([
    function (Get $get): array {
        $eventId = $get('event_id');
        $startsAt = $get('starts_at');
        $recordId = $get('id'); // Get current record ID for edit

        if ($eventId && $startsAt) {
            return [
                new AvailableTalkSchedule(
                    eventId: (int) $eventId,
                    start_at: (string) $startsAt,
                    excludeTalkId: $recordId ? (int) $recordId : null,
                ),
            ];
        }

        return [];
    },
])

7. Add Model-Level Validation

File: app-modules/events/src/Models/Talk.php

Add validation to ensure data integrity:

protected static function booted(): void
{
    static::saving(function (Talk $talk) {
        // Ensure start is before end
        if ($talk->starts_at >= $talk->ends_at) {
            throw new \InvalidArgumentException(
                'Start time must be before end time'
            );
        }
        
        // Ensure talk is within event boundaries
        $event = $talk->event;
        if ($event) {
            if ($talk->starts_at < $event->start_at || $talk->ends_at > $event->end_at) {
                throw new \InvalidArgumentException(
                    'Talk must be scheduled within event boundaries'
                );
            }
        }
    });
}

🧪 Testing Improvements

8. Add Edge Case Tests

File: app-modules/events/tests/Feature/Filament/App/Talks/CreateTalkTest.php

Add tests for:

it('should not allow talks that partially overlap', function (): void {
    $existingStart = now()->addHour();
    $existingEnd = now()->addHours(3);
    
    Talk::factory()->recycle($this->event)->create([
        'starts_at' => $existingStart,
        'ends_at' => $existingEnd,
        'status' => TalkStatusEnum::Accepted,
    ]);
    
    // Start during existing talk
    livewire(CreateTalk::class)
        ->fillForm([
            'event_id' => $this->event->getKey(),
            'starts_at' => $existingStart->copy()->addMinutes(30),
            'ends_at' => $existingEnd->copy()->addHour(),
            // ... other fields
        ])
        ->call('create')
        ->assertHasFormErrors(['ends_at']);
});

it('should allow back-to-back talks', function (): void {
    $existingStart = now()->addHour();
    $existingEnd = now()->addHours(2);
    
    Talk::factory()->recycle($this->event)->create([
        'starts_at' => $existingStart,
        'ends_at' => $existingEnd,
        'status' => TalkStatusEnum::Accepted,
    ]);
    
    // Start exactly when previous ends
    livewire(CreateTalk::class)
        ->fillForm([
            'event_id' => $this->event->getKey(),
            'starts_at' => $existingEnd,
            'ends_at' => $existingEnd->copy()->addHour(),
            // ... other fields
        ])
        ->call('create')
        ->assertHasNoFormErrors();
});

it('should not allow talks outside event boundaries', function (): void {
    livewire(CreateTalk::class)
        ->fillForm([
            'event_id' => $this->event->getKey(),
            'starts_at' => $this->event->end_at->addHour(),
            'ends_at' => $this->event->end_at->addHours(2),
            // ... other fields
        ])
        ->call('create')
        ->assertHasFormErrors(['starts_at']);
});

📚 Additional Enhancements

9. Add Timezone Handling

Consider adding timezone support in the Talk model:

protected $casts = [
    'status' => TalkStatusEnum::class,
    'starts_at' => 'datetime:Y-m-d H:i:s',
    'ends_at' => 'datetime:Y-m-d H:i:s',
];

// Add accessor for user's timezone
public function getStartsAtInTimezoneAttribute(): Carbon
{
    return $this->starts_at->timezone(auth()->user()->timezone ?? 'America/Sao_Paulo');
}

10. Add Duration Validation

Add reasonable limits for talk duration:

// In StartEndFieldsSchema.php
DateTimePicker::make('ends_at')
    // ... existing rules ...
    ->rules([
        function (Get $get): array {
            $rules = [];
            $startsAt = $get('starts_at');
            
            if ($startsAt) {
                // Max 4 hours
                $rules[] = Rule::when($startsAt, function () use ($get) {
                    $start = Carbon::parse($get('starts_at'));
                    $maxEnd = $start->copy()->addHours(4);
                    return "before_or_equal:{$maxEnd}";
                });
            }
            
            return $rules;
        },
    ])

These improvements will make the feature more robust, performant, and maintainable! Let me know if you'd like me to help implement any of these suggestions.

@danielhe4rt danielhe4rt marked this pull request as ready for review November 18, 2025 17:46
@danielhe4rt danielhe4rt merged commit cd3dde3 into 3.x Nov 18, 2025
5 of 6 checks passed
@danielhe4rt danielhe4rt deleted the feat/event-talks branch November 18, 2025 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants