Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backend/app/DomainObjects/EventDomainObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public static function getAllowedSorts(): AllowedSorts
{
return new AllowedSorts(
[
self::START_DATE => [
'asc' => __('Closest start date'),
'desc' => __('Furthest start date'),
],
self::CREATED_AT => [
'desc' => __('Newest first'),
'asc' => __('Oldest first'),
Expand Down
31 changes: 27 additions & 4 deletions backend/app/Repository/Eloquent/EventRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,14 @@ public function findEvents(array $where, QueryParamsDTO $params): LengthAwarePag
};
}

$this->model = $this->model->orderBy(
$this->validateSortColumn($params->sort_by, EventDomainObject::class),
$this->validateSortDirection($params->sort_direction, EventDomainObject::class),
);
$sortColumn = $this->validateSortColumn($params->sort_by, EventDomainObject::class);
$sortDirection = $this->validateSortDirection($params->sort_direction, EventDomainObject::class);

if ($sortColumn === EventDomainObjectAbstract::START_DATE) {
$this->applyOccurrenceStartDateSort($sortDirection, $upcomingEventsFilter, $endedEventsFilter);
} else {
$this->model = $this->model->orderBy($sortColumn, $sortDirection);
}

return $this->paginateWhere(
where: $where,
Expand All @@ -123,6 +127,25 @@ public function findEvents(array $where, QueryParamsDTO $params): LengthAwarePag
);
}

private function applyOccurrenceStartDateSort(string $direction, bool $upcomingOnly, bool $endedOnly): void
{
$liveOccurrences = 'FROM event_occurrences eo WHERE eo.event_id = events.id AND eo.deleted_at IS NULL';
$bindings = [];

if ($upcomingOnly) {
$sortDateSql = "SELECT MIN(eo.start_date) {$liveOccurrences} AND COALESCE(eo.end_date, eo.start_date) >= ?";
$bindings[] = now();
} elseif ($endedOnly) {
$sortDateSql = "SELECT MAX(eo.start_date) {$liveOccurrences}";
} else {
$sortDateSql = "SELECT MIN(eo.start_date) {$liveOccurrences}";
}

$this->model = $this->model
->orderByRaw("({$sortDateSql}) {$direction} NULLS LAST", $bindings)
->orderBy(EventDomainObjectAbstract::ID);
}

public function getUpcomingEventsForAdmin(int $perPage): LengthAwarePaginator
{
$now = now();
Expand Down
72 changes: 65 additions & 7 deletions backend/tests/Feature/Repository/Eloquent/EventRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,70 @@ public function test_get_all_events_for_admin_sorts_by_earliest_occurrence_start
$this->assertLessThan(array_search($this->eventWithoutOccurrencesId, $ids, true), array_search($lateId, $ids, true));
}

private function findEventIds(string $eventsStatus): array
public function test_start_date_sort_orders_events_chronologically_not_by_creation_order(): void
{
$earlierStartId = $this->createEvent('Chrono earlier '.uniqid(), createdAt: now()->subDays(2));
$laterStartId = $this->createEvent('Chrono later '.uniqid(), createdAt: now()->subDay());
$this->createOccurrence($earlierStartId, now()->addDays(5), now()->addDays(5)->addHours(2));
$this->createOccurrence($laterStartId, now()->addDays(10), now()->addDays(10)->addHours(2));

$ids = $this->findEventIds('upcoming', sortBy: 'start_date', sortDirection: 'asc');

$this->assertLessThan(array_search($laterStartId, $ids, true), array_search($earlierStartId, $ids, true));
}

public function test_upcoming_start_date_sort_uses_next_upcoming_occurrence(): void
{
$partiallyElapsedId = $this->createEvent('Partially elapsed '.uniqid());
$singleUpcomingId = $this->createEvent('Single upcoming '.uniqid());
$this->createOccurrence($partiallyElapsedId, now()->subDays(30), now()->subDays(30)->addHours(2));
$this->createOccurrence($partiallyElapsedId, now()->addDays(10), now()->addDays(10)->addHours(2));
$this->createOccurrence($singleUpcomingId, now()->addDays(5), now()->addDays(5)->addHours(2));

$ids = $this->findEventIds('upcoming', sortBy: 'start_date', sortDirection: 'asc');

$this->assertLessThan(array_search($partiallyElapsedId, $ids, true), array_search($singleUpcomingId, $ids, true));
}

public function test_upcoming_start_date_sort_puts_events_without_occurrences_last(): void
{
$withOccurrenceId = $this->createEvent('Has occurrence '.uniqid());
$this->createOccurrence($withOccurrenceId, now()->addDays(60), now()->addDays(60)->addHours(2));

$ids = $this->findEventIds('upcoming', sortBy: 'start_date', sortDirection: 'asc');

$this->assertLessThan(array_search($this->eventWithoutOccurrencesId, $ids, true), array_search($withOccurrenceId, $ids, true));
}

public function test_ended_start_date_sort_desc_puts_most_recent_past_event_first(): void
{
$olderPastId = $this->createEvent('Older past '.uniqid(), createdAt: now()->subDay());
$recentPastId = $this->createEvent('Recent past '.uniqid(), createdAt: now()->subDays(2));
$this->createOccurrence($olderPastId, now()->subDays(20), now()->subDays(20)->addHours(2));
$this->createOccurrence($recentPastId, now()->subDays(40), now()->subDays(40)->addHours(2));
$this->createOccurrence($recentPastId, now()->subDays(5), now()->subDays(5)->addHours(2));

$ids = $this->findEventIds('ended', sortBy: 'start_date', sortDirection: 'desc');

$this->assertLessThan(array_search($olderPastId, $ids, true), array_search($recentPastId, $ids, true));
}

public function test_unknown_sort_column_falls_back_to_created_at_desc(): void
{
$olderId = $this->createEvent('Fallback older '.uniqid(), createdAt: now()->subDays(3));
$newerId = $this->createEvent('Fallback newer '.uniqid(), createdAt: now()->subDay());

$ids = $this->findEventIds('upcoming', sortBy: 'not_a_column', sortDirection: 'desc');

$this->assertLessThan(array_search($olderId, $ids, true), array_search($newerId, $ids, true));
}

private function findEventIds(string $eventsStatus, string $sortBy = 'created_at', string $sortDirection = 'desc'): array
{
$params = QueryParamsDTO::fromArray([
'eventsStatus' => $eventsStatus,
'sort_by' => 'created_at',
'sort_direction' => 'desc',
'sort_by' => $sortBy,
'sort_direction' => $sortDirection,
'per_page' => 100,
]);

Expand All @@ -174,9 +232,9 @@ private function findEventIds(string $eventsStatus): array
->all();
}

private function createEvent(string $title, string $status = 'DRAFT'): int
private function createEvent(string $title, string $status = 'DRAFT', $createdAt = null): int
{
$now = now()->toDateTimeString();
$createdAt = ($createdAt ?? now())->toDateTimeString();

return DB::table('events')->insertGetId([
'title' => $title,
Expand All @@ -187,8 +245,8 @@ private function createEvent(string $title, string $status = 'DRAFT'): int
'currency' => 'USD',
'timezone' => 'UTC',
'short_id' => 'evt_'.uniqid(),
'created_at' => $now,
'updated_at' => $now,
'created_at' => $createdAt,
'updated_at' => $createdAt,
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const EventCard: React.FC<EventCardProps> = ({event, primaryColor = '#8b5
const emojiIndex = event.id ? Number(event.id) % placeholderEmojis.length : 0;
const placeholderEmoji = placeholderEmojis[emojiIndex];

const hasStartDate = !!event.start_date;
const startMonth = formatDateWithLocale(event.start_date, "monthShort", event.timezone);
const startDay = formatDateWithLocale(event.start_date, "dayOfMonth", event.timezone);
const startTime = formatDateWithLocale(event.start_date, "timeOnly", event.timezone);
Expand Down Expand Up @@ -57,7 +58,7 @@ export const EventCard: React.FC<EventCardProps> = ({event, primaryColor = '#8b5
const now = dayjs();
const startDate = dayjs(event.start_date);
const endDate = event.end_date ? dayjs(event.end_date) : startDate.add(2, 'hour');
const isLive = now.isAfter(startDate) && now.isBefore(endDate);
const isLive = hasStartDate && now.isAfter(startDate) && now.isBefore(endDate);

const products = getProductsFromEvent(event) || [];

Expand Down Expand Up @@ -134,31 +135,33 @@ export const EventCard: React.FC<EventCardProps> = ({event, primaryColor = '#8b5

<div className={classes.dateBadge}>
<IconCalendar size={16}/>
<span>{startMonth} {startDay}</span>
<span>{hasStartDate ? `${startMonth} ${startDay}` : t`Date TBA`}</span>
</div>
</div>

<div className={classes.eventContent}>
<div className={classes.eventHeader}>
<h3 className={classes.eventTitle}>{event.title}</h3>

<div className={classes.eventDateTime}>
<IconClock size={14}/>
<span>
{startTime}
{endTime && (
<>
{!isSameDay
? ` - ${endMonth} ${endDay}, ${endTime}`
: ` - ${endTime}`
}
</>
)}
{prettyTimezone && (
<span title={event.timezone} className={classes.timezone}> ({prettyTimezone})</span>
)}
</span>
</div>
{hasStartDate && (
<div className={classes.eventDateTime}>
<IconClock size={14}/>
<span>
{startTime}
{endTime && (
<>
{!isSameDay
? ` - ${endMonth} ${endDay}, ${endTime}`
: ` - ${endTime}`
}
</>
)}
{prettyTimezone && (
<span title={event.timezone} className={classes.timezone}> ({prettyTimezone})</span>
)}
</span>
</div>
)}
</div>

{event.description_preview && (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/locales/de.js

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions frontend/src/locales/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,10 @@ msgstr "Termin reaktiviert"
msgid "Date reopened for new sales"
msgstr "Termin wieder für den Verkauf geöffnet"

#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:138
msgid "Date TBA"
msgstr "Datum folgt"

#: src/components/routes/event/OccurrencesTab/OccurrenceEditModal/index.tsx:254
msgid "Date updated successfully"
msgstr "Termin erfolgreich aktualisiert"
Expand Down Expand Up @@ -5264,7 +5268,7 @@ msgstr "Vierter"
#: src/components/common/ProductsTable/SortableProduct/index.tsx:121
#: src/components/common/ProductsTable/SortableProduct/index.tsx:131
#: src/components/forms/ProductForm/index.tsx:275
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:188
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:191
#: src/components/routes/product-widget/SelectProducts/Prices/Tiered/index.tsx:154
#: src/components/routes/product-widget/SelectProducts/Prices/Tiered/index.tsx:225
msgid "Free"
Expand Down Expand Up @@ -6287,7 +6291,7 @@ msgstr "Liste"
msgid "Live"
msgstr "Live"

#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:120
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:121
msgid "LIVE"
msgstr "LIVE"

Expand Down Expand Up @@ -6734,7 +6738,7 @@ msgstr "Mehrzeiliges Textfeld"

#: src/components/common/EventCard/index.tsx:100
#: src/components/layouts/EventHomepage/index.tsx:180
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:46
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:47
msgid "Multiple locations"
msgstr "Mehrere Orte"

Expand Down Expand Up @@ -7494,7 +7498,7 @@ msgid "Ongoing"
msgstr "Laufend"

#: src/components/common/EventCard/index.tsx:103
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:49
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:50
#: src/components/routes/event/OccurrencesTab/index.tsx:318
#: src/components/routes/event/OccurrencesTab/OccurrenceEditModal/index.tsx:473
#: src/components/routes/my-tickets/index.tsx:56
Expand All @@ -7510,7 +7514,7 @@ msgid "Online — provide connection details"
msgstr "Online – Zugangsdaten angeben"

#: src/components/common/EventCard/index.tsx:100
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:46
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:47
msgid "Online & in-person"
msgstr "Online & vor Ort"

Expand All @@ -7524,7 +7528,7 @@ msgid "Online event"
msgstr "Online-Veranstaltung"

#: src/components/layouts/EventHomepage/index.tsx:404
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:175
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:178
msgid "Online Event"
msgstr "Online-Veranstaltung"

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/locales/el.js

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions frontend/src/locales/el.po
Original file line number Diff line number Diff line change
Expand Up @@ -3400,6 +3400,10 @@ msgstr "Η ημερομηνία επανενεργοποιήθηκε"
msgid "Date reopened for new sales"
msgstr "Η ημερομηνία άνοιξε ξανά για πωλήσεις"

#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:138
msgid "Date TBA"
msgstr "Ημερομηνία σύντομα"

#: src/components/routes/event/OccurrencesTab/OccurrenceEditModal/index.tsx:254
msgid "Date updated successfully"
msgstr "Η ημερομηνία ενημερώθηκε με επιτυχία"
Expand Down Expand Up @@ -5264,7 +5268,7 @@ msgstr "4η"
#: src/components/common/ProductsTable/SortableProduct/index.tsx:121
#: src/components/common/ProductsTable/SortableProduct/index.tsx:131
#: src/components/forms/ProductForm/index.tsx:275
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:188
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:191
#: src/components/routes/product-widget/SelectProducts/Prices/Tiered/index.tsx:154
#: src/components/routes/product-widget/SelectProducts/Prices/Tiered/index.tsx:225
msgid "Free"
Expand Down Expand Up @@ -6287,7 +6291,7 @@ msgstr "Λίστα"
msgid "Live"
msgstr "Δημοσιευμένο"

#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:120
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:121
msgid "LIVE"
msgstr "ΔΗΜΟΣΙΕΥΜΕΝΟ"

Expand Down Expand Up @@ -6734,7 +6738,7 @@ msgstr "Πεδίο κειμένου πολλαπλών γραμμών"

#: src/components/common/EventCard/index.tsx:100
#: src/components/layouts/EventHomepage/index.tsx:180
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:46
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:47
msgid "Multiple locations"
msgstr "Πολλές τοποθεσίες"

Expand Down Expand Up @@ -7494,7 +7498,7 @@ msgid "Ongoing"
msgstr "Σε Εξέλιξη"

#: src/components/common/EventCard/index.tsx:103
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:49
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:50
#: src/components/routes/event/OccurrencesTab/index.tsx:318
#: src/components/routes/event/OccurrencesTab/OccurrenceEditModal/index.tsx:473
#: src/components/routes/my-tickets/index.tsx:56
Expand All @@ -7510,7 +7514,7 @@ msgid "Online — provide connection details"
msgstr "Διαδικτυακά — δώστε στοιχεία σύνδεσης"

#: src/components/common/EventCard/index.tsx:100
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:46
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:47
msgid "Online & in-person"
msgstr "Διαδικτυακά και με φυσική παρουσία"

Expand All @@ -7524,7 +7528,7 @@ msgid "Online event"
msgstr "Διαδικτυακή εκδήλωση"

#: src/components/layouts/EventHomepage/index.tsx:404
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:175
#: src/components/layouts/OrganizerHomepage/EventCard/index.tsx:178
msgid "Online Event"
msgstr "Διαδικτυακή Εκδήλωση"

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/locales/en.js

Large diffs are not rendered by default.

Loading
Loading