Skip to content

Commit

Permalink
Templates modal implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
thecalcc committed Feb 8, 2024
1 parent 218767b commit baf92d2
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 8 deletions.
42 changes: 34 additions & 8 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} from '../../selectors/events';
import {Dropdown, IDropdownItem} from '../UI/SubNav';
import {showModal} from '@superdesk/common';
import PlanningTemplatesModal from '../../components/PlanningTemplatesModal';

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

class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {
Expand Down Expand Up @@ -43,14 +46,36 @@ class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {
id: 'create_event',
});

this.props.eventTemplates.forEach((template) => {
items.push({
label: template.template_name,
icon: 'icon-event icon--blue',
group: gettext('From template'),
action: () => createEventFromTemplate(template),
id: template._id,
this.props.eventTemplates
.sort((templ1, templ2) => templ1.template_name.localeCompare(templ2.template_name))
.slice(0, 5)
.forEach((template) => {
items.push({
label: template.template_name,
icon: 'icon-event icon--blue',
group: gettext('From template'),
action: () => createEventFromTemplate(template),
id: template._id,
});
});

items.push({
label: gettext('More templates...'),
icon: 'icon-event icon--blue',
group: gettext('From template'),
action: () => {
showModal(({closeModal}) => (
<PlanningTemplatesModal
createEventFromTemplate={createEventFromTemplate}
closeModal={closeModal}
calendars={this.props.calendars}
eventTemplates={this.props.eventTemplates
.sort((templ1, templ2) => templ1.template_name.localeCompare(templ2.template_name))
}
/>
));
},
id: 'more_templates',
});
}

Expand Down Expand Up @@ -79,6 +104,7 @@ class CreateNewSubnavDropdownFn extends React.PureComponent<IProps> {

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

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;

type ICloseModalProps = {
closeModal: () => void;
}

const TemplatesListView: React.FC<ICloseModalProps> = ({closeModal}) => {
const calendarFilter = (template: IEventTemplate) => this.state.activeCalendarFilter
? template.data.calendars.map(({qcode}) => qcode).includes(this.state.activeCalendarFilter)
: template;
/**
* Groups the templates by calendar, filters the templates that match the current search query,
* filters the templates by the current selected calendar if one is selected,
* filters out the groups that don't have any templates
*/
const filteredTemplates = calendars.map((calendar) => ({
calendarName: calendar.name,
templates: eventTemplates
.filter((template) => template.template_name.includes(this.state.searchQuery))
.filter((template) => template.data.calendars.map(({qcode}) => qcode).includes(calendar.qcode))
.filter(calendarFilter)
})).filter((templatesGroup) => templatesGroup.templates.length > 0);

return (
<>
{
filteredTemplates.map(({calendarName, templates}) => (
<React.Fragment key={calendarName}>
<Heading type="h6" className="mt-2 mb-1">{calendarName}</Heading>
<BoxedList>
{templates
.map((template) => (
<BoxedListItem
key={template._id}
clickable={true}
onClick={() => {
createEventFromTemplate(template);
closeModal();
}}
>
{template.template_name}
</BoxedListItem>
))
}
</BoxedList>
</React.Fragment>
))
}
</>
);
};

return (
<Modal
headerTemplate={gettext('Planning templates')}
visible
contentPadding="medium"
contentBg="medium"
size="medium"
onHide={closeModal}
>
<div className="modal__sticky-header">
<Spacer h gap="0" noWrap>
<TreeSelect
width="match-input"
zIndex={3000}
value={this.state.activeCalendarFilter
? [this.props.calendars.find(({qcode}) => this.state.activeCalendarFilter === qcode)]
: []
}
kind="synchronous"
labelHidden
inlineLabel
getOptions={() => calendars.map((calendar) => ({value: calendar}))}
getLabel={(item) => item.name}
getId={(item) => item.qcode}
placeholder={gettext('All Calendars')}
optionTemplate={(item: any) => <div>{item.name}</div>}
valueTemplate={(item: any, Wrapper) => (
<Wrapper>
<span>{item.name}</span>
</Wrapper>
)}
onChange={([value]) => {
if (value?.qcode != null) {
this.setState({
activeCalendarFilter: value?.qcode,
searchQuery: '',
});
} else {
this.setState({
activeCalendarFilter: value?.qcode,
});
}
}}
/>
<SearchBar
value={this.state.searchQuery}
onSubmit={(value: string) => {
this.setState({
activeCalendarFilter: undefined,
searchQuery: value,
});
}}
placeholder={gettext('Search templates')}
boxed
/>
</Spacer>
<TemplatesListView closeModal={closeModal} />
</div>
</Modal>
);
}
}

0 comments on commit baf92d2

Please sign in to comment.