Skip to content

Commit

Permalink
Templates modal implementation (superdesk#1909)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecalcc authored Feb 12, 2024
1 parent 34c07b7 commit 238298b
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 18 deletions.
2 changes: 1 addition & 1 deletion client/actions/events/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ const addEventRecentTemplate = (field: string, templateId: IEventTemplate['_id']
const getEventsRecentTemplates = () => (
(dispatch, getState, {preferencesService}) => preferencesService.get()
.then((result) => {
const templates = take(result[RECENT_EVENTS_TEMPLATES_KEY]['templates'], 5);
const templates = take(result?.[RECENT_EVENTS_TEMPLATES_KEY]?.['templates'], 5);

dispatch({type: EVENTS.ACTIONS.EVENT_RECENT_TEMPLATES, payload: templates});
})
Expand Down
43 changes: 32 additions & 11 deletions client/components/Main/CreateNewSubnavDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import React from 'react';
import {connect} from 'react-redux';

import {superdeskApi} from '../../superdeskApi';
import {IEventTemplate} from '../../interfaces';
import {ICalendar, IEventTemplate} from '../../interfaces';

import {PRIVILEGES} from '../../constants';
import * as actions from '../../actions';
import {eventTemplates, getRecentTemplatesSelector} from '../../selectors/events';
import {Dropdown, IDropdownItem} from '../UI/SubNav';
import {showModal} from '@superdesk/common';
import PlanningTemplatesModal from '../PlanningTemplatesModal/PlanningTemplatesModal';

interface IProps {
addEvent(): void;
Expand All @@ -16,9 +18,12 @@ interface IProps {
privileges: {[key: string]: number};
createEventFromTemplate(template: IEventTemplate): void;
eventTemplates: Array<IEventTemplate>;
calendars: Array<ICalendar>;
recentTemplates?: Array<IEventTemplate>;
}

const MORE_TEMPLATES_THRESHOLD = 5;

class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {
render() {
const {gettext} = superdeskApi.localization;
Expand Down Expand Up @@ -52,26 +57,41 @@ class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {
id: 'create_event',
});

if (recentTemplates.length !== 0) {
recentTemplates.forEach((template) => {
/**
* Sort the templates by their name.
*/
const sortedTemplates = eventTemplates
.sort((templ1, templ2) => templ1.template_name.localeCompare(templ2.template_name));

recentTemplates
.forEach((template) => {
items.push({
label: template.template_name,
icon: 'icon-event icon--blue',
group: gettext('Recent Templates'),
group: gettext('From template'),
action: () => createEventFromTemplate(template),
id: template._id,
});
});
}
eventTemplates.forEach((template) => {

if (sortedTemplates.length > MORE_TEMPLATES_THRESHOLD) {
items.push({
label: template.template_name,
label: gettext('More templates...'),
icon: 'icon-event icon--blue',
group: gettext('ALL Templates'),
action: () => createEventFromTemplate(template),
id: template._id,
group: gettext('From template'),
action: () => {
showModal(({closeModal}) => (
<PlanningTemplatesModal
createEventFromTemplate={createEventFromTemplate}
closeModal={closeModal}
calendars={this.props.calendars}
eventTemplates={sortedTemplates}
/>
));
},
id: 'more_templates',
});
});
}
}

return items.length === 0 ? null : (
Expand Down Expand Up @@ -99,6 +119,7 @@ class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {

function mapStateToProps(state) {
return {
calendars: state.events.calendars,
eventTemplates: eventTemplates(state),
recentTemplates: getRecentTemplatesSelector(state)
};
Expand Down
105 changes: 105 additions & 0 deletions client/components/PlanningTemplatesModal/PlanningTemplatesModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {ICalendar, IEventTemplate} from '../../interfaces';
import React from 'react';
import {SearchBar, Modal, Dropdown} from 'superdesk-ui-framework/react';
import {superdeskApi} from '../../superdeskApi';
import {TemplatesListView} from './TemplatesListView';

interface IProps {
closeModal: () => void;
calendars: Array<ICalendar>;
eventTemplates: Array<IEventTemplate>;
createEventFromTemplate(template: IEventTemplate): void;
}

interface IState {
activeCalendarFilter?: string;
searchQuery: string;
}

export default class PlanningTemplatesModal extends React.Component<IProps, IState> {
constructor(props) {
super(props);

this.state = {
activeCalendarFilter: null,
searchQuery: '',
};
}

render(): React.ReactNode {
const {gettext} = superdeskApi.localization;
const {closeModal, createEventFromTemplate, calendars, eventTemplates} = this.props;
const allCalendarsLabel = gettext('All Calendars');
const calendarDropdownItems = [];
const activeCalendarName = this.props.calendars
.find((cal) => cal.qcode === this.state.activeCalendarFilter)?.name;
const dropdownLabel = this.state.activeCalendarFilter
? gettext('Calendar: {{activeCalendarName}}', {activeCalendarName})
: allCalendarsLabel;

if (this.state.activeCalendarFilter) {
calendarDropdownItems.push({
label: allCalendarsLabel,
onSelect: () => {
this.setState({
activeCalendarFilter: null,
});
}
});
}

if ((calendars?.length ?? 0) > 0) {
calendarDropdownItems.push(
...calendars.map((calendar) => ({
label: calendar.name,
onSelect: () => {
this.setState({
activeCalendarFilter: calendar.qcode,
});
}
}))
);
}

return (
<Modal
headerTemplate={gettext('Planning templates')}
visible
contentPadding="medium"
contentBg="medium"
size="medium"
onHide={closeModal}
>
<div className="modal__sticky-header">
<SearchBar
value={this.state.searchQuery}
onSubmit={(value: string) => {
this.setState({
searchQuery: value,
});
}}
placeholder={gettext('Search templates')}
boxed
>
<Dropdown
maxHeight={300}
append
zIndex={2001}
items={calendarDropdownItems}
>
{dropdownLabel}
</Dropdown>
</SearchBar>
<TemplatesListView
calendars={calendars}
createEventFromTemplate={createEventFromTemplate}
eventTemplates={eventTemplates}
searchQuery={this.state.searchQuery}
activeCalendarFilter={this.state.activeCalendarFilter}
closeModal={closeModal}
/>
</div>
</Modal>
);
}
}
79 changes: 79 additions & 0 deletions client/components/PlanningTemplatesModal/TemplatesListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {ICalendar, IEventTemplate} from 'interfaces';
import React from 'react';
import {gettext} from 'superdesk-core/scripts/core/utils';
import {Heading, BoxedList, BoxedListItem} from 'superdesk-ui-framework/react';

type ITemplatesListViewProps = {
closeModal: () => void;
eventTemplates: Array<IEventTemplate>;
calendars: Array<ICalendar>;
activeCalendarFilter?: string;
searchQuery: string;
createEventFromTemplate: (template: IEventTemplate) => void;
}

export const TemplatesListView: React.FC<ITemplatesListViewProps> = ({
eventTemplates,
calendars,
closeModal,
activeCalendarFilter,
searchQuery,
createEventFromTemplate,
}: ITemplatesListViewProps) => {
const searchQueryTemplateMatches = eventTemplates
.filter((template) => template.template_name.includes(searchQuery));
const calendarsFiltered = activeCalendarFilter
? [calendars.find(({qcode}) => activeCalendarFilter === qcode)]
: calendars;

const filteredTemplates = calendarsFiltered
.map((_calendar) => ({
calendar: _calendar,
templates: searchQueryTemplateMatches
.filter((template) => template.data.calendars.find(({qcode}) => qcode === _calendar.qcode)),
}))
.filter((group) => activeCalendarFilter
? group.calendar.qcode === activeCalendarFilter
: group.templates.length > 0
);

return (
<>
{filteredTemplates.map(({calendar, templates}) => (
<React.Fragment key={calendar.qcode}>
<Heading type="h6" className="mt-2 mb-1">{calendar.name}</Heading>
{
templates?.length > 0 ? (
<BoxedList>
{templates.map((template) => (
<BoxedListItem
key={template._id}
clickable={true}
onClick={() => {
createEventFromTemplate(template);
closeModal();
}}
>
{template.template_name}
</BoxedListItem>
))}
</BoxedList>
) : (
<BoxedListItem clickable={false}>
{gettext('No templates available in this calendar group.')}
</BoxedListItem>
)
}

</React.Fragment>
))}
{activeCalendarFilter == null && searchQuery && filteredTemplates.length === 0 && (
<div className="mt-2 mb-1">
<BoxedListItem clickable={false}>
{gettext('No templates found.')}
</BoxedListItem>
</div>
)}
</>
);
};
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"sinon": "^4.5.0",
"superdesk-code-style": "1.5.0",
"superdesk-core": "github:superdesk/superdesk-client-core#develop",
"superdesk-ui-framework": "^3.0.71",
"superdesk-ui-framework": "^3.0.72",
"ts-node": "~7.0.1",
"tslint": "5.11.0",
"typescript-eslint-parser": "^18.0.0"
Expand Down

0 comments on commit 238298b

Please sign in to comment.