1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

(wip) React component [EventCard]

This commit is contained in:
vincent 2022-04-14 18:30:56 +02:00 committed by Sylvain
parent a5cdd33e0b
commit fcfa9513e8
5 changed files with 320 additions and 0 deletions

View File

@ -0,0 +1,131 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { react2angular } from 'react2angular';
import { IApplication } from '../../models/application';
import { Loader } from '../base/loader';
import { Event } from '../../models/event';
import FormatLib from '../../lib/format';
declare const Application: IApplication;
interface EventCardProps {
event: Event,
cardType: 'sm' | 'md' | 'lg'
}
export const EventCard: React.FC<EventCardProps> = ({ event, cardType = 'sm' }) => {
const { t } = useTranslation('public');
console.log(event);
/**
* Format description to remove HTML tags and set a maximum character count
*/
const formatText = (text: string, count: number) => {
text = text.replace(/(<\/p>|<\/h4>|<\/h5>|<\/h6>|<\/pre>|<\/blockquote>)/g, '\n');
text = text.replace(/<br\s*\/?>/g, '\n');
text = text.replace(/<\/?\w+[^>]*>/g, '');
text = text.replace(/\n+/g, '<br />');
if (text.length > count) {
return text.slice(0, count) + '...';
}
return text;
};
/**
* Return the formatted localized date of the event
*/
const formatDate = (): string => {
// FIXME: typeof event.all_day = sting ?
return event.all_day === 'true'
? t('app.public.home.from_date_to_date', { START: FormatLib.date(event.start_date), END: FormatLib.date(event.end_date) })
: t('app.public.home.on_the_date', { DATE: FormatLib.date(event.start_date) });
};
/**
* Return the formatted localized hours of the event
*/
const formatTime = (): string => {
// FIXME: typeof event.all_day = sting ?
return event.all_day === 'true'
? t('app.public.home.all_day')
: t('app.public.home.from_time_to_time', { START: FormatLib.time(event.start_date), END: FormatLib.time(event.end_date) });
};
/**
* Link to event by id
*/
const showEvent = (id: number) => {
// TODO: ???
console.log(id);
};
return (
<div className={`event-card event-card--${cardType}`} onClick={() => showEvent(event.id)}>
<div className="event-card-picture">
{event.event_image
? <img src={event.event_image} alt="" />
: <i className="fas fa-image"></i>
}
</div>
<div className="event-card-desc">
<header>
<p className='title'>{event?.title}</p>
<span className={`badge bg-${event.category.slug}`}>{event.category.name}</span>
</header>
{cardType !== 'sm' &&
<p dangerouslySetInnerHTML={{ __html: formatText(event.description, 500) }}></p>
}
</div>
<div className="event-card-info">
{cardType !== 'md' && <p>{formatDate()}</p> }
<div className="grid">
{cardType === 'sm' &&
event.event_themes.map(theme => {
return (<div key={theme.name} className="grid-item">
<i className="fa fa-tags"></i>
<h6>{theme.name}</h6>
</div>);
})
}
{(cardType === 'sm' && event.age_range) &&
<div className="grid-item">
<i className="fa fa-users"></i>
<h6>{event.age_range?.name}</h6>
</div>
}
{cardType === 'md' &&
<div className="grid-item">
<i className="fa fa-calendar"></i>
<h6>{formatDate()}</h6>
</div>
}
<div className="grid-item">
{cardType !== 'sm' && <i className="fa fa-clock"></i>}
<h6>{formatTime()}</h6>
</div>
<div className="grid-item">
{cardType !== 'sm' && <i className="fa fa-user"></i>}
{event.nb_free_places > 0 && <h6>{t('app.public.home.still_available') + event.nb_free_places}</h6>}
{event.nb_total_places > 0 && event.nb_free_places <= 0 && <h6>{t('app.public.home.event_full')}</h6>}
{!event.nb_total_places && <h6>{t('app.public.home.without_reservation')}</h6>}
</div>
<div className="grid-item">
{cardType !== 'sm' && <i className="fa fa-bookmark"></i>}
{event.amount === 0 && <h6>{t('app.public.home.free_admission')}</h6>}
{/* TODO: Display currency ? */}
{event.amount > 0 && <h6>{t('app.public.home.full_price') + event.amount}</h6>}
</div>
</div>
</div>
</div>
);
};
const EventCardWrapper: React.FC<EventCardProps> = ({ event, cardType }) => {
return (
<Loader>
<EventCard event={event} cardType={cardType} />
</Loader>
);
};
Application.Components.component('eventCard', react2angular(EventCardWrapper, ['event', 'cardType']));

View File

@ -29,6 +29,7 @@
@import "modules/base/fab-text-editor";
@import "modules/base/labelled-input";
@import "modules/calendar/calendar";
@import "modules/events/event";
@import "modules/form/form-input";
@import "modules/form/form-item";
@import "modules/form/form-rich-text";

View File

@ -0,0 +1,182 @@
.event {
&-card {
border: 1px solid var(--gray-soft-dark);
border-radius: 5px;
overflow: hidden;
color: var(--gray-hard-darkest);
&:hover {
color: var(--gray-hard-darkest);
cursor: pointer;
& .event-card-picture {opacity: 0.7;}
}
&-picture {
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
font-size: 8rem;
color: var(--gray-soft-light);
transition: opacity 0.4s ease-out;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&-desc {
padding: 15px 15px 30px;
header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
p {
margin: 0;
font-size: 2rem;
font-weight: 900;
line-height: 1.3;
text-transform: uppercase;
}
}
p { margin: 0; }
}
&-info {
margin-top: auto;
padding: 15px 30px;
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
&-item {
height: 20px;
display: flex;
align-items: center;
i {
width: 16px;
height: 16px;
margin-right: 15px;
font-size: 16px;
text-align: center;
color: var(--main);
}
}
}
}
}
// Card size specific
&-card--sm {
display: grid;
grid-template-columns: 1fr max-content;
.event-card-desc {
grid-area: 1/1/2/2;
padding-bottom: 10px;
header p { font-size: 1.6rem; }
}
.event-card-picture {
grid-area: 1/2/3/3;
width: 160px;
display: none;
border-left: 1px solid var(--gray-soft-dark);
@media (min-width: 500px) {
display: flex;
}
}
.event-card-info {
grid-area: 2/1/3/2;
padding: 0 15px 15px;
& > p {
font-size: 1.8rem;
font-weight: 600;
color: var(--main);
}
.grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 5px 10px;
&-item i {
width: 12px;
height: 12px;
margin-right: 6px;
font-size: 12px;
text-align: center;
color: var(--gray-hard-light);
}
}
}
}
&-card--md {
display: grid;
grid-template-rows: min-content 1fr min-content;
.event-card-picture {
height: 250px;
border-bottom: 1px solid var(--gray-soft-dark);
}
.event-card-info {
padding-bottom: 30px;
border-top: 1px solid var(--gray-soft-dark);
}
}
&-card--lg {}
// layout specific
&-home {
h4 {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
column-gap: 1rem;
i { margin-right: 1rem;}
}
&-list {
display: grid;
grid-template-columns: 1fr;
gap: 30px;
margin-bottom: 5rem;
@media (min-width: 480px) {
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
}
}
}
&-focus {
display: grid;
margin-bottom: 50px;
.event-card {
border: 1px solid var(--gray-hard);
&-info { padding-top: 0; }
&-picture {
border-bottom: 1px solid var(--gray-hard);
img { max-height: 300px; }
}
}
@media (min-width: 992px) {
grid-template-columns: repeat(auto-fill, minmax(425px, 1fr));
gap: 15px;
.event-card {
grid-column: span 2;
display: grid;
grid-template-columns: 3fr 2fr;
&-desc { grid-area: 1/1/2/2; }
&-info { grid-area: 2/1/3/2; }
&-picture {
grid-area: 1/2/3/3;
border-bottom: none;
border-left: 1px solid var(--gray-hard);
img { max-height: none; }
}
}
}
}
&-month-list {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
margin-bottom: 2rem;
@media (min-width: 768px) {
grid-template-columns: repeat(auto-fill, minmax(425px, 1fr));
}
}
}

View File

@ -74,6 +74,8 @@
<img ng-src="{{event.event_image_small}}" title="{{event.title}}">
</div>
</a>
<event-card style="display: contents" event="event" card-type="sm" ng-repeat="event in eventsGroupByMonth[month].slice(3*$index, 3*$index + 3)" />
</div>
</div>

View File

@ -50,4 +50,8 @@
</div>
</div>
</div>
<div class="event-home-list" ng-repeat="event in (upcomingEvents.length/3 | array)">
<event-card style="display: contents" event="event" card-type="md" ng-repeat="event in upcomingEvents.slice(3*$index, 3*$index + 3)" />
</div>
</section>