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:
parent
a5cdd33e0b
commit
fcfa9513e8
131
app/frontend/src/javascript/components/events/event-card.tsx
Normal file
131
app/frontend/src/javascript/components/events/event-card.tsx
Normal 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']));
|
@ -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";
|
||||
|
182
app/frontend/src/stylesheets/modules/events/event.scss
Normal file
182
app/frontend/src/stylesheets/modules/events/event.scss
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user