Skip to content

Commit

Permalink
FEATURE: upcoming events list component (#463)
Browse files Browse the repository at this point in the history
* FEATURE: upcoming events list component
  • Loading branch information
renato authored Oct 30, 2023
1 parent b8a8a24 commit f57b09c
Show file tree
Hide file tree
Showing 6 changed files with 532 additions and 0 deletions.
178 changes: 178 additions & 0 deletions assets/javascripts/discourse/components/upcoming-events-list.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import DButton from "discourse/components/d-button";
import Component from "@glimmer/component";
import { ajax } from "discourse/lib/ajax";
import I18n from "discourse-i18n";
import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import { isNotFullDayEvent } from "../lib/guess-best-date-format";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import or from "truth-helpers/helpers/or";

export const DEFAULT_MONTH_FORMAT = "MMMM YYYY";
export const DEFAULT_DATE_FORMAT = "dddd, MMM D";
export const DEFAULT_TIME_FORMAT = "LT";

export default class UpcomingEventsList extends Component {
@service appEvents;
@service siteSettings;
@service router;

@tracked isLoading = true;
@tracked hasError = false;
@tracked eventsByMonth = {};

monthFormat = this.args.params?.monthFormat ?? DEFAULT_MONTH_FORMAT;
dateFormat = this.args.params?.dateFormat ?? DEFAULT_DATE_FORMAT;
timeFormat = this.args.params?.timeFormat ?? DEFAULT_TIME_FORMAT;

title = I18n.t("discourse_post_event.upcoming_events_list.title");
emptyMessage = I18n.t("discourse_post_event.upcoming_events_list.empty");
allDayLabel = I18n.t("discourse_post_event.upcoming_events_list.all_day");
errorMessage = I18n.t("discourse_post_event.upcoming_events_list.error");

constructor() {
super(...arguments);

this.appEvents.on("page:changed", this, this.updateEventsByMonth);
}

get shouldRender() {
if (!this.categoryId) {
return false;
}

const eventSettings =
this.siteSettings.events_calendar_categories.split("|");

return eventSettings.includes(this.categoryId.toString());
}

get categoryId() {
return this.router.currentRoute.attributes?.category?.id;
}

get hasEmptyResponse() {
return (
!this.isLoading &&
!this.hasError &&
Object.keys(this.eventsByMonth).length === 0
);
}

@action
async updateEventsByMonth() {
this.isLoading = true;
this.hasError = false;

try {
const { events } = await ajax("/discourse-post-event/events", {
data: { category_id: this.categoryId },
});

this.eventsByMonth = this.groupByMonthAndDay(events);
} catch {
this.hasError = true;
} finally {
this.isLoading = false;
}
}

@action
formatMonth(month) {
return moment(month, "YYYY-MM").format(this.monthFormat);
}

@action
formatDate(month, day) {
return moment(`${month}-${day}`, "YYYY-MM-DD").format(this.dateFormat);
}

@action
formatTime({ starts_at, ends_at }) {
return isNotFullDayEvent(moment(starts_at), moment(ends_at))
? moment(starts_at).format(this.timeFormat)
: this.allDayLabel;
}

groupByMonthAndDay(data) {
return data.reduce((result, item) => {
const date = new Date(item.starts_at);
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();

const monthKey = `${year}-${month}`;

result[monthKey] = result[monthKey] ?? {};
result[monthKey][day] = result[monthKey][day] ?? [];

result[monthKey][day].push(item);

return result;
}, {});
}

<template>
{{#if this.shouldRender}}
<div class="upcoming-events-list">
<h3 class="upcoming-events-list__heading">
{{this.title}}
</h3>

<div class="upcoming-events-list__container">
<ConditionalLoadingSpinner @condition={{this.isLoading}} />

{{#if this.hasEmptyResponse}}
<div class="upcoming-events-list__empty-message">
{{this.emptyMessage}}
</div>
{{/if}}

{{#if this.hasError}}
<div class="upcoming-events-list__error-message">
{{this.errorMessage}}
</div>
<DButton
@action={{this.updateEventsByMonth}}
@label="discourse_post_event.upcoming_events_list.try_again"
class="btn-link upcoming-events-list__try-again"
/>
{{/if}}

{{#unless this.isLoading}}
{{#each-in this.eventsByMonth as |month monthData|}}
{{#if this.monthFormat}}
<h4 class="upcoming-events-list__formatted-month">
{{this.formatMonth month}}
</h4>
{{/if}}

{{#each-in monthData as |day events|}}
<div class="upcoming-events-list__day-section">
<div class="upcoming-events-list__formatted-day">
{{this.formatDate month day}}
</div>

{{#each events as |event|}}
<a
class="upcoming-events-list__event"
href={{event.post.url}}
>
<div class="upcoming-events-list__event-time">
{{this.formatTime event}}
</div>
<div class="upcoming-events-list__event-name">
{{or event.name event.post.topic.title}}
</div>
</a>
{{/each}}
</div>
{{/each-in}}
{{/each-in}}
{{/unless}}
</div>
</div>
{{/if}}
</template>
}
2 changes: 2 additions & 0 deletions assets/stylesheets/common/upcoming-events-calendar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
background: var(--tertiary);
color: var(--secondary);
}

margin: 0.3em 0 0.3em 0.5em;
}

Expand Down Expand Up @@ -122,6 +123,7 @@
margin-left: 0;
}
}

.fc-list-item-add-to-calendar {
color: var(--tertiary);
font-size: var(--font-down-1);
Expand Down
36 changes: 36 additions & 0 deletions assets/stylesheets/common/upcoming-events-list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.upcoming-events-list {
&__formatted-month {
font-weight: 600;
}

&__day-section {
display: flex;
flex-direction: column;
padding-bottom: 0.5rem;
gap: 0.5rem;
}

&__formatted-day {
margin-left: 0.5rem;
font-weight: 600;
font-size: var(--base-font-size);
}

&__event {
display: flex;
align-items: center;
margin-left: 1rem;
gap: 0.25rem;
font-size: var(--font-down-1);
line-height: var(--line-height-medium);
}

&__event-name {
width: 70%;
}

&__event-time {
width: 30%;
text-align: center;
}
}
6 changes: 6 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,12 @@ en:
creator: "Creator"
status: "Status"
starts_at: "Starts at"
upcoming_events_list:
title: "Upcoming events"
empty: "No upcoming events"
all_day: "All-day"
error: "Failed to retrieve events"
try_again: "Try again"
category:
sort_topics_by_event_start_date: "Sort topics by event start date."
disable_topic_resorting: "Disable topic resorting."
Expand Down
1 change: 1 addition & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
register_asset "stylesheets/desktop/discourse-calendar.scss", :desktop
register_asset "stylesheets/colors.scss", :color_definitions
register_asset "stylesheets/common/user-preferences.scss"
register_asset "stylesheets/common/upcoming-events-list.scss"
register_svg_icon "fas fa-calendar-day"
register_svg_icon "fas fa-clock"
register_svg_icon "fas fa-file-csv"
Expand Down
Loading

0 comments on commit f57b09c

Please sign in to comment.