From 0c13b9ca48d4323c64a2996a68f846f78a86e789 Mon Sep 17 00:00:00 2001 From: Ketan <73937490+devketanpro@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:21:27 +0530 Subject: [PATCH] Update Agenda preview to display multiple Events [STT-53] (#1212) * Update Agenda preview to display multiple Events [STT-53] * minor chnage * address comment * refactore code * fix lint * format code * update types --- assets/agenda/actions.ts | 12 + .../agenda/components/AgendaPreviewEvent.tsx | 182 ++++++------ .../components/AgendaPreviewPlanning.tsx | 265 +++++++++++++----- assets/agenda/utils.ts | 2 +- assets/interfaces/agenda.ts | 2 + 5 files changed, 314 insertions(+), 149 deletions(-) diff --git a/assets/agenda/actions.ts b/assets/agenda/actions.ts index afb577ab5..190a4a2bc 100644 --- a/assets/agenda/actions.ts +++ b/assets/agenda/actions.ts @@ -399,6 +399,18 @@ export function fetchItem(id: any) { }; } +export function fetchItemsById(ids: Array): Promise> { + return server.get(`/agenda/search?ids=${ids.join(', ')}`); +} + +export function fetchItemsByIdToRedux(ids: Array): (dispatch: any) => Promise { + return (dispatch: any) => { + return fetchItemsById(ids).then((response) => { + dispatch(recieveItem(response)); + }); + }; +} + export const WATCH_EVENTS = 'WATCH_EVENTS'; export function watchEvents(ids: any) { return (dispatch: any, getState: any) => { diff --git a/assets/agenda/components/AgendaPreviewEvent.tsx b/assets/agenda/components/AgendaPreviewEvent.tsx index fae081830..6986b3d23 100644 --- a/assets/agenda/components/AgendaPreviewEvent.tsx +++ b/assets/agenda/components/AgendaPreviewEvent.tsx @@ -1,12 +1,10 @@ import * as React from 'react'; import {connect} from 'react-redux'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {get} from 'lodash'; import {gettext} from 'utils'; import {getName, getInternalNote} from '../utils'; -import {fetchItem} from '../actions'; +import {fetchItemsByIdToRedux} from '../actions'; import AgendaTime from './AgendaTime'; import AgendaListItemLabels from './AgendaListItemLabels'; @@ -16,118 +14,146 @@ import AgendaPreviewAttachments from './AgendaPreviewAttachments'; import AgendaTags from './AgendaTags'; import AgendaEdNote from './AgendaEdNote'; import AgendaInternalNote from './AgendaInternalNote'; +import {IAgendaItem} from 'interfaces'; -class AgendaPreviewEventComponent extends React.Component { - static propTypes: any; - constructor(props: any) { +interface AgendaPreviewEventProps { + item: IAgendaItem; + itemsById: Record; + eventIds: Array; + fetchItemsByIdToRedux: (ids: Array) => Promise; +} + +interface AgendaPreviewEventState { + loading: boolean; + expandedEvents: Record; +} + +class AgendaPreviewEventComponent extends React.Component { + constructor(props: AgendaPreviewEventProps) { super(props); this.state = { loading: true, - expanded: false, + expandedEvents: {}, }; + this.toggleExpanded = this.toggleExpanded.bind(this); + this.reloadEvent = this.reloadEvent.bind(this); } componentDidMount() { this.reloadEvent(); } - componentDidUpdate(prevProps: any) { - if (get(prevProps.item, 'event_id') !== get(this.props.item, 'event_id')) { + componentDidUpdate(prevProps: AgendaPreviewEventProps) { + if (prevProps.eventIds !== this.props.eventIds) { this.reloadEvent(); } } reloadEvent() { - this.setState({loading: true}, () => { - this.props - .fetchEvent(this.props.item.event_id) - .finally(() => { - this.setState({loading: false}); - }); - }); + const {eventIds, fetchItemsByIdToRedux} = this.props; + + if (eventIds == null || eventIds.length == 0) { + return; + } + + + this.setState({loading: true}); + + fetchItemsByIdToRedux(eventIds) + .finally(() => { + this.setState({loading: false}); + }) + .catch((error) => { + console.error('Error fetching items:', error); + this.setState({loading: false}); + }); + } + + toggleExpanded(eventId: string) { + this.setState((prevState) => ({ + expandedEvents: { + ...prevState.expandedEvents, + [eventId]: !prevState.expandedEvents[eventId], + }, + })); } - toggleExpanded() { - this.setState((prevState: any) => ({expanded: !prevState.expanded})); + renderEvent(item: IAgendaItem) { + const isExpanded = this.state.expandedEvents[item._id] || false; + + return ( +
+
+ this.toggleExpanded(item._id)}> + + +

this.toggleExpanded(item._id)}>{getName(item)}

+
+
+ + + +
+ {!isExpanded ? null : ( +
+ + + + + + +
+ )} +
+ ); } render() { - if (!this.state.loading && this.props.event == null) { - // If we're not loading and there is no event, - // then an error has occurred (user already notified via props.fetchEvent) - return null; - } + const {itemsById, eventIds} = this.props; return (
- - {gettext('Associated Event')} - -
- {this.state.loading ? ( -
- ) : ( - -
- - - -

{getName(this.props.event)}

-
-
- - - -
- {!this.state.expanded ? null : ( -
- - - - - - -
- )} -
- )} -
+ {gettext('Related Events')} + {this.state.loading ? ( +
+ ) : ( + eventIds + .map((id) => itemsById[id]) + .filter((event) => event != null) + .map((event) => this.renderEvent(event)) + )}
); } } -AgendaPreviewEventComponent.propTypes = { - item: PropTypes.object, - event: PropTypes.object, - fetchEvent: PropTypes.func, -}; - const mapStateToProps = (state: any, ownProps: any) => ({ - event: state.itemsById[ownProps.item.event_id], + itemsById: state.itemsById, + eventIds: ownProps.item.event_ids || [], }); const mapDispatchToProps = (dispatch: any) => ({ - fetchEvent: (eventId: any) => dispatch(fetchItem(eventId)), + fetchItemsByIdToRedux: (ids: Array) => dispatch(fetchItemsByIdToRedux(ids)), }); export const AgendaPreviewEvent = connect(mapStateToProps, mapDispatchToProps)(AgendaPreviewEventComponent); diff --git a/assets/agenda/components/AgendaPreviewPlanning.tsx b/assets/agenda/components/AgendaPreviewPlanning.tsx index 8225f850a..5367f91a0 100644 --- a/assets/agenda/components/AgendaPreviewPlanning.tsx +++ b/assets/agenda/components/AgendaPreviewPlanning.tsx @@ -3,6 +3,7 @@ import {connect} from 'react-redux'; import {gettext} from 'utils'; import {isPlanningItem} from '../utils'; +import {fetchItemsByIdToRedux} from '../actions'; import AgendaPreviewCoverages from './AgendaPreviewCoverages'; import {IAgendaItem, ICoverageItemAction, IUser, IAgendaPreviewConfig, IArticle, IAgendaState} from 'interfaces'; @@ -14,97 +15,221 @@ interface IOwnProps { previewGroup?: string; restrictCoverageInfo?: boolean; previewConfig: IAgendaPreviewConfig; + planningItems?: Array; + fetchItemsByIdToRedux: (ids: Array) => Promise; } interface IReduxStateProps { wireItems?: Array; + items: Array; +} + +interface IState { + loading: boolean; + expandedPlanningItems: Record; } type IProps = IOwnProps & IReduxStateProps; -function AgendaPreviewPlanningComponent({ - item, - planningId, - wireItems, - coverageActions, - user, - previewGroup, - restrictCoverageInfo, - previewConfig, -}: IProps) { - const planningItems = item.planning_items || []; - const plan = planningItems.find((p) => p.guid === planningId); - const otherPlanningItems = planningItems.filter((p) => p.guid !== planningId); - - if (isPlanningItem(item) || restrictCoverageInfo) { - return ( - - ); +class AgendaPreviewPlanningComponent extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + loading: false, + expandedPlanningItems: {}, + }; + + this.fetchSecondaryPlanningItems = this.fetchSecondaryPlanningItems.bind(this); + this.toggleExpanded = this.toggleExpanded.bind(this); } - return ( - - {!plan ? null : ( -
-
- - {gettext('Planning Item')} - - -
-
- )} - {!otherPlanningItems.length ? null : ( -
-
- - {plan == null ? gettext('Planning Items') : gettext('Other Planning Items')} - - {otherPlanningItems.map((planningItem) => ( + componentDidMount() { + this.fetchSecondaryPlanningItems(); + } + + componentDidUpdate(prevProps: IProps) { + if (prevProps.item !== this.props.item) { + this.fetchSecondaryPlanningItems(); + } + } + + fetchSecondaryPlanningItems() { + const {item, planningId, fetchItemsByIdToRedux} = this.props; + const planningIds = item.planning_ids?.filter((id:string) => id !== planningId) || []; + + this.setState({loading: true}); + + if (planningIds == null || planningIds.length == 0) { + return; + } + + this.setState({loading: true}); + + fetchItemsByIdToRedux(planningIds) + .finally(() => { + this.setState({loading: false}); + }) + .catch((error) => { + console.error('Error fetching items:', error); + this.setState({loading: false}); + }); + } + + toggleExpanded(planningItemId: string) { + this.setState((prevState) => ({ + expandedPlanningItems: { + ...prevState.expandedPlanningItems, + [planningItemId]: !prevState.expandedPlanningItems[planningItemId], + }, + })); + } + + getRelatedPlanningItems() { + const {item, items} = this.props; + return (item.planning_ids || []).map((id:any) => items[id]); + } + + render() { + const { + item, + planningId, + wireItems, + coverageActions, + user, + previewGroup, + restrictCoverageInfo, + previewConfig, + } = this.props; + const {loading, expandedPlanningItems} = this.state; + + const planningItems = item.planning_items || []; + const plan = planningItems.find((p) => p.guid === planningId); + const otherPlanningItems = planningItems.filter((p) => p.guid !== planningId); + const relatedPlanningItems = this.getRelatedPlanningItems(); + + if (isPlanningItem(item) || restrictCoverageInfo) { + return ( + + ); + } + + return ( + + {/* Current Planning Items */} + {!plan ? null : ( +
+
+ {gettext('Planning Item')} - ))} +
+
+ )} + {!otherPlanningItems.length ? null : ( +
+
+ + {plan == null ? gettext('Planning Items') : gettext('Other Planning Items')} + + {otherPlanningItems.map((planningItem) => ( + + ))} +
+
+ )} + {/* Related Planning Items */} +
+
+ + {gettext('Related Planning Items')} + + {loading ? ( +
+ ) : ( + relatedPlanningItems && relatedPlanningItems.map((planningItem: any) => { + const isExpanded = expandedPlanningItems[planningItem._id] || false; + return ( +
+
+ this.toggleExpanded(planningItem._id) + } + > + + {planningItem.headline || 'No Headline'} +
+ {isExpanded && ( + + )} +
+ ); + }) + )}
- )} - - ); + + ); + } } -const mapStateToProps = (state: IAgendaState): IReduxStateProps => ({ - wireItems: state.agenda.agendaWireItems || [], +const mapStateToProps = (state: any, ownProps: any) => { + return { + items: state.itemsById, + wireItems: state.agenda.agendaWireItems || [], + }; +}; + +const mapDispatchToProps = (dispatch: any) => ({ + fetchItemsByIdToRedux: (ids: Array) => dispatch(fetchItemsByIdToRedux(ids)), }); -export const AgendaPreviewPlanning = connect< - IReduxStateProps, - {}, - IOwnProps, - IAgendaState ->(mapStateToProps)(AgendaPreviewPlanningComponent); +export const AgendaPreviewPlanning = connect(mapStateToProps, mapDispatchToProps)(AgendaPreviewPlanningComponent); diff --git a/assets/agenda/utils.ts b/assets/agenda/utils.ts index dd0db0144..20f8e8bca 100644 --- a/assets/agenda/utils.ts +++ b/assets/agenda/utils.ts @@ -321,7 +321,7 @@ export function isPlanningItem(item: any) { } export function planHasEvent(item: any) { - return isPlanningItem(item) && item.event_id != null; + return isPlanningItem(item) && item.event_ids != null; } /** diff --git a/assets/interfaces/agenda.ts b/assets/interfaces/agenda.ts index b5eb5cabc..26c426f3c 100644 --- a/assets/interfaces/agenda.ts +++ b/assets/interfaces/agenda.ts @@ -224,6 +224,8 @@ export interface IAgendaItem extends IResourceItem { firstcreated: string; versioncreated: string; internal_note?: string; + planning_ids?: Array; + event_ids?: Array; } export interface IPlanningItem extends Omit {