From 9bfc27f9e758a9f580da1dfc91a8d7718216f9b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 10 Jun 2024 18:54:29 -0300 Subject: [PATCH] refactor editable table to allow to reuse on other pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/event-actions.js | 2 +- ...ventsEditableTable.js => EditableTable.js} | 63 ++-- ...ableHeading.js => EditableTableHeading.js} | 6 +- .../tables/editable-table/EditableTableRow.js | 121 ++++++++ .../editable-table/EventsEditableTableRow.js | 281 ------------------ src/pages/events/summit-event-list-page.js | 65 +++- 6 files changed, 202 insertions(+), 336 deletions(-) rename src/components/tables/editable-table/{EventsEditableTable.js => EditableTable.js} (76%) rename src/components/tables/editable-table/{EventsEditableTableHeading.js => EditableTableHeading.js} (88%) create mode 100644 src/components/tables/editable-table/EditableTableRow.js delete mode 100644 src/components/tables/editable-table/EventsEditableTableRow.js diff --git a/src/actions/event-actions.js b/src/actions/event-actions.js index 656d64631..c398a5c8e 100644 --- a/src/actions/event-actions.js +++ b/src/actions/event-actions.js @@ -828,7 +828,7 @@ export const normalizeBulkEvents = (entity) => { id: e.id, title: e.title, selection_plan_id: e.selection_plan_id, - location_id: e.location.id, + location_id: e.location?.id || e.location_id, start_date: e.start_date, speakers: e.speakers, end_date: e.end_date, diff --git a/src/components/tables/editable-table/EventsEditableTable.js b/src/components/tables/editable-table/EditableTable.js similarity index 76% rename from src/components/tables/editable-table/EventsEditableTable.js rename to src/components/tables/editable-table/EditableTable.js index f2e061420..422638cf9 100644 --- a/src/components/tables/editable-table/EventsEditableTable.js +++ b/src/components/tables/editable-table/EditableTable.js @@ -1,13 +1,7 @@ import React, { useState, useEffect } from "react"; -import EditableTableHeading from "./EventsEditableTableHeading"; -import EventsEditableTableRow from "./EventsEditableTableRow"; +import EditableTableHeading from "./EditableTableHeading"; +import EditableTableRow from "./EditableTableRow"; import ReactTooltip from "react-tooltip"; -import { - getAllEventTypes, - getAllTrackCategory, - getAllLocations, - getAllSelectionPlans, -} from "../../../utils/summitUtils"; import T from "i18n-react/dist/i18n-react"; const defaults = { @@ -18,16 +12,16 @@ const defaults = { colWidth: "", }; -const EventsEditableTable = (props) => { +const EditableTable = (props) => { const { options, columns, currentSummit, page, - events, + data, handleSort, - updateEvents, - handleDeleteEvent, + updateData, + handleDeleteRow, resetData, } = props; let tableClass = options.hasOwnProperty("className") ? options.className : ""; @@ -35,11 +29,7 @@ const EventsEditableTable = (props) => { const [editEnabled, setEditEnabled] = useState(false); const [selected, setSelected] = useState([]); const [selectAll, setSelectAll] = useState(false); - tableClass += options.actions?.edit ? " table-hover" : ""; - - const activityTypeOptions = getAllEventTypes(currentSummit); - const activtyCategoryOptions = getAllTrackCategory(currentSummit); - const selectionPlanOptions = getAllSelectionPlans(currentSummit); + tableClass += options.actions?.edit ? " table-hover" : ""; const getSortDir = (columnKey, columnIndex, sortCol, sortDir) => { if (columnKey && columnKey === sortCol) { @@ -75,7 +65,7 @@ const EventsEditableTable = (props) => { useEffect(() => { if (selectAll) { - setSelected(events); + setSelected(data); setSelectAll(true); } else { setSelectAll(false); @@ -83,31 +73,31 @@ const EventsEditableTable = (props) => { } }, [selectAll]); - const updateSelected = (event, checked) => { - let selectedEvent = event; - const eventIndex = selected.findIndex((s) => s.id === selectedEvent.id); - let exists = eventIndex !== -1; + const updateSelected = (row, checked) => { + let selectedRow = row; + const rowIndex = selected.findIndex((s) => s.id === selectedRow.id); + let exists = rowIndex !== -1; if (checked) { if (exists) { // if already on selected list, replace with new data - const updatedSelected = Object.assign({}, selectedEvent); + const updatedSelected = Object.assign({}, selectedRow); const newSelected = selected.slice(); - newSelected[eventIndex] = updatedSelected; + newSelected[rowIndex] = updatedSelected; setSelected(newSelected); } else { // append to list - setSelected((currSelected) => [...currSelected, selectedEvent]); + setSelected((currSelected) => [...currSelected, selectedRow]); } } else { - setSelected(selected.filter((se) => se.id !== selectedEvent.id)); + setSelected(selected.filter((se) => se.id !== selectedRow.id)); } }; const onUpdateEvents = (evt) => { evt.stopPropagation(); evt.preventDefault(); - updateEvents(currentSummit.id, selected); + updateData(currentSummit.id, selected); resetState(); }; @@ -200,27 +190,24 @@ const EventsEditableTable = (props) => { {columns.length > 0 && - events.map((event, i) => { - if (Array.isArray(event) && event.length !== columns.length) { + data.map((row, i) => { + if (Array.isArray(row) && row.length !== columns.length) { console.warn( - `Data at row ${i} is ${event.length}. It should be ${columns.length}.` + `Data at row ${i} is ${row.length}. It should be ${columns.length}.` ); return ; } return ( - - + @@ -234,4 +221,4 @@ const EventsEditableTable = (props) => { ); }; -export default EventsEditableTable; +export default EditableTable; diff --git a/src/components/tables/editable-table/EventsEditableTableHeading.js b/src/components/tables/editable-table/EditableTableHeading.js similarity index 88% rename from src/components/tables/editable-table/EventsEditableTableHeading.js rename to src/components/tables/editable-table/EditableTableHeading.js index 1d9344eb7..e543f6746 100644 --- a/src/components/tables/editable-table/EventsEditableTableHeading.js +++ b/src/components/tables/editable-table/EditableTableHeading.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; -const EventsEditableTableHeading = (props) => { +const EditableTableHeading = (props) => { const { editEnabled, sortable, @@ -41,7 +41,7 @@ const EventsEditableTableHeading = (props) => { ); }; -EventsEditableTableHeading.propTypes = { +EditableTableHeading.propTypes = { onSort: PropTypes.func, sortDir: PropTypes.number, columnIndex: PropTypes.number, @@ -50,4 +50,4 @@ EventsEditableTableHeading.propTypes = { sortFunc: PropTypes.func, }; -export default EventsEditableTableHeading; +export default EditableTableHeading; diff --git a/src/components/tables/editable-table/EditableTableRow.js b/src/components/tables/editable-table/EditableTableRow.js new file mode 100644 index 000000000..929658bed --- /dev/null +++ b/src/components/tables/editable-table/EditableTableRow.js @@ -0,0 +1,121 @@ +import React, { useEffect, useState } from "react"; +import { Input } from "openstack-uicore-foundation/lib/components"; +import T from "i18n-react/dist/i18n-react"; +import history from "../../../history"; + +const EditableTableRow = (props) => { + const { + row, + columns, + editEnabled, + selected, + updateSelected, + deleteRow, + selectAll, + currentSummit, + actions, + } = props; + const [checked, setChecked] = useState(false); + const [editData, setEditData] = useState(row); + + useEffect(() => { + updateSelected(editData, checked); + }, [checked, row]); + useEffect(() => { + setChecked(selectAll); + }, [selectAll]); + useEffect(() => { + if (selected.length === 0) { + setChecked(false); + } + }, [selected]); + useEffect(() => { + updateSelected(editData, checked); + }, [editData]) + useEffect(() => { + if (!editEnabled) { + setEditData(row); + } + }, [editEnabled]); + + const onRowChange = (ev) => { + const {value, id, type} = ev.target; + if(type === 'speakerinput') { + const newSpeakers = {...editData, [id]: [...row[id], value]}; + setEditData(newSpeakers); + } else { + const newEventData = {...editData, [id]: value }; + setEditData(newEventData); + } + + }; + + const onRemoveOption = (rowId, id) => { + const newOptions = row[id].filter(s => s.id !== rowId); + const newEventData = {...editData, [id]: newOptions}; + setEditData(newEventData); + }; + + return ( + <> + + setChecked(!checked)} + checked={checked} + /> + + {row.id} + {selected.find((s) => s.id === row.id) && editEnabled && checked ? ( + <> + {columns.map((col, index) => { + if(col.columnKey === "id") { + return; + } + else if(col.editableField === true) { + // Default field as text + return ( + + + + )} + else if (col.editableField) { + return ( + + {col.editableField({value: "", placeholder: row[col.columnKey].name, onChange: onRowChange, rowData: editData[col.columnKey], onRemoveOption: onRemoveOption})} + + ) + } + else {return ({row[col.columnKey]})} + })} + + ) : columns.map((col, i) => + col.columnKey !== "id" && {col.render ? col.render(row[col.columnKey]) : row[col.columnKey]}) + } + {(actions.edit || actions.delete) && ( + + {actions.edit && ( + history.push(`/app/summits/${currentSummit.id}/events/${row.id}`)}> + + + )} + {actions.delete && ( + deleteRow(row.id)}> + + + )} + + )} + + ); +}; + +export default EditableTableRow; diff --git a/src/components/tables/editable-table/EventsEditableTableRow.js b/src/components/tables/editable-table/EventsEditableTableRow.js deleted file mode 100644 index 732fa1f14..000000000 --- a/src/components/tables/editable-table/EventsEditableTableRow.js +++ /dev/null @@ -1,281 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { FormGroup, FormControl } from "react-bootstrap"; -import { Dropdown, Input, SpeakerInput } from "openstack-uicore-foundation/lib/components"; -import T from "i18n-react/dist/i18n-react"; -import history from "../../../history"; -import { flattenEventData } from "../../../utils/summitUtils"; - -const EventsEditableTableRow = (props) => { - const { - event, - columns, - editEnabled, - selected, - updateSelected, - deleteEvent, - selectAll, - currentSummit, - selectionPlanOptions, - activityTypeOptions, - activtyCategoryOptions, - actions, - } = props; - const [checked, setChecked] = useState(false); - const speakersDefault = event.speakers?.length > 0 ? event.speakers : []; - const [speakersList, setSpeakers] = useState(speakersDefault); - const [editData, setEditData] = useState(event) - - const dataDisplay = flattenEventData(event, currentSummit); - - useEffect(() => { - updateSelected(editData, checked); - }, [checked, event]); - useEffect(() => { - setChecked(selectAll); - }, [selectAll]); - useEffect(() => { - if (selected.length === 0) { - setChecked(false); - } - }, [selected]); - useEffect(() => { - const newEventData = {...editData, speakers: speakersList }; - setEditData(newEventData); - }, [speakersList]) - useEffect(() => { - updateSelected(editData, checked); - }, [editData]) - useEffect(() => { - if (!editEnabled) { - setSpeakers(speakersDefault); - } - }, [editEnabled]); - - const onActivityTypeChange = (ev) => { - const type_id = activityTypeOptions.filter((a) => a.value === ev.target.value)[0] - ?.value; - const newEventData = {...editData, type_id: type_id}; - setEditData(newEventData); - }; - const onTitleChange = (ev) => { - const title = ev.target.value; - const newEventData = {...editData, title }; - setEditData(newEventData); - }; - const onSpeakersChange = (ev) => { - const speakers = ev.target.value; - setSpeakers([...speakersList, speakers]); - }; - const onRemoveSpeaker = (speakerId) => { - const newSpeakers = speakersList.filter(s => s.id !== speakerId); - setSpeakers(newSpeakers); - const newEventData = {...editData, speakers: newSpeakers}; - setEditData(newEventData); - }; - const onActivityCategoryChange = (ev) => { - const track_id = activtyCategoryOptions.filter((a) => a.value === ev.target.value)[0] - ?.value; - const newEventData = {...editData, track_id: track_id }; - setEditData(newEventData); - }; - const onSelectionPlanChange = (option) => { - const selection_plan_id = selectionPlanOptions.filter(s => s.value === option.target.value)[0].value; - const newEventData = {...editData, selection_plan_id: selection_plan_id }; - setEditData(newEventData); - }; - const onStreamingURLChange = (ev) => { - const streaming_url = ev.target.value; - const newEventData = {...editData, streaming_url}; - setEditData(newEventData); - }; - const onMeetingURLChange = (ev) => { - const meeting_url = ev.target.value; - const newEventData = {...editData, meeting_url}; - setEditData(newEventData); - }; - const onEtherpadURLChange = (ev) => { - const etherpad_link = ev.target.value; - const newEventData = {...editData, etherpad_link}; - setEditData(newEventData); - }; - - return ( - <> - - setChecked(!checked)} - checked={checked} - /> - - {event.id} - {selected.find((s) => s.id === event.id) && editEnabled && checked ? ( - <> - {columns.map(col => { - if(col.columnKey === "id") { - return; - } - else if (col.columnKey === "event_type") { - return ( - - - at.value === event.type?.id).label || - T.translate("bulk_actions_page.placeholders.event_type") - } - value={""} - onChange={onActivityTypeChange} - options={activityTypeOptions} - /> - - - - ) - } - else if(col.columnKey === "title") { - return ( - - - - - - - )} - else if(col.columnKey === "speakers") { - return ( - - `${speaker.first_name} ${speaker.last_name} (${speaker.email})`} - /> -
- {speakersList?.length > 0 && speakersList.map(sp =>
{`${sp?.first_name} ${sp?.last_name}`} - onRemoveSpeaker(sp.id)} /> -
)} -
- - )} - else if(col.columnKey === "track") { - return ( - - - ac.value === event.track?.id)?.label || - T.translate("bulk_actions_page.placeholders.track") - } - value={""} - onChange={onActivityCategoryChange} - options={activtyCategoryOptions} - /> - - - - )} - else if(col.columnKey === "selection_plan") { - return ( - - - sp.id === event.selection_plan?.id)?.label) || - T.translate( - "schedule.placeholders.select_presentation_selection_plan" - ) - } - value={""} - onChange={onSelectionPlanChange} - options={selectionPlanOptions} - /> - - - - )} - else if(col.columnKey === "streaming_url") { - return ( - - - - - - - )} - else if(col.columnKey === "meeting_url") { - return ( - - - - - - - )} - else if(col.columnKey === "etherpad_link") { - return ( - - - - - - - )} - else {return ({event[col.columKey]})} - })} - - ) : columns.map((col, i) => - col.columnKey !== "id" && {dataDisplay[col.columnKey]}) - } - {(actions.edit || actions.delete) && ( - - {actions.edit && ( - history.push(`/app/summits/${currentSummit.id}/events/${event.id}`)}> - - - )} - {actions.delete && ( - deleteEvent(event.id)}> - - - )} - - )} - - ); -}; - -export default EventsEditableTableRow; diff --git a/src/pages/events/summit-event-list-page.js b/src/pages/events/summit-event-list-page.js index 4b6ccd25f..c74f322d2 100644 --- a/src/pages/events/summit-event-list-page.js +++ b/src/pages/events/summit-event-list-page.js @@ -50,16 +50,38 @@ import SaveFilterCriteria from '../../components/filters/save-filter-criteria'; import SelectFilterCriteria from '../../components/filters/select-filter-criteria'; import { saveFilterCriteria, deleteFilterCriteria } from '../../actions/filter-criteria-actions'; import { CONTEXT_ACTIVITIES } from '../../utils/filter-criteria-constants'; -import EventsEditableTable from "../../components/tables/editable-table/EventsEditableTable"; - -const fieldNames = [ - { columnKey: "speakers", value: "speakers" }, +import EditableTable from "../../components/tables/editable-table/EditableTable"; + +const fieldNames = (selection_plans_ddl, track_ddl) => [ + { columnKey: "speakers", value: "speakers", editableField: (extraProps) => (<> + `${speaker.first_name} ${speaker.last_name} (${speaker.email})`} + {...extraProps} + /> +
+ {extraProps.rowData?.length > 0 && extraProps.rowData.map(sp => ( +
{`${sp?.first_name} ${sp?.last_name}`} + extraProps.onRemoveOption(sp.id, 'speakers')} /> +
+ ))} +
+ + ), render: (field) => field.length > 0 ? field.map(s => `${s.first_name} ${s.last_name}`).join(', ') : 'N/A' }, { columnKey: "created_by_fullname", value: "created_by", sortable: true }, { columnKey: "published_date", value: "published", sortable: true }, { columnKey: "duration", value: "duration", sortable: true }, { columnKey: "speakers_count", value: "speakers_count", sortable: true }, { columnKey: "speaker_company", value: "speaker_company", sortable: true }, - { columnKey: "track", value: "track", sortable: true }, + { columnKey: "track", value: "track", sortable: true, editableField: (extraProps) => (), render: (field) => field?.name }, { columnKey: "start_date", value: "start_date", sortable: true }, { columnKey: "end_date", value: "end_date", sortable: true }, { columnKey: "submitters", value: "submitters" }, @@ -70,7 +92,12 @@ const fieldNames = [ }, { columnKey: "sponsor", value: "sponsor", sortable: true }, { columnKey: "event_type_capacity", value: "event_type_capacity" }, - { columnKey: "selection_plan", value: "selection_plan", sortable: true }, + { columnKey: "selection_plan", value: "selection_plan", sortable: true, editableField: (extraProps) => (), render: (field) => field?.name }, { columnKey: "location", value: "location", sortable: true }, { columnKey: "level", value: "level", sortable: true }, { columnKey: "tags", value: "tags", sortable: true }, @@ -79,18 +106,21 @@ const fieldNames = [ value: "streaming_url", sortable: true, title: true, + editableField: true }, { columnKey: "meeting_url", value: "meeting_url", sortable: true, title: true, + editableField: true }, { columnKey: "etherpad_link", value: "etherpad_link", sortable: true, title: true, + editableField: true }, { columnKey: "streaming_type", value: "streaming_type", sortable: true }, { @@ -485,8 +515,15 @@ class SummitEventListPage extends React.Component { let columns = [ { columnKey: 'id', value: T.translate("general.id"), sortable: true }, - { columnKey: 'event_type', value: T.translate("event_list.type"), sortable: true }, - { columnKey: 'title', value: T.translate("event_list.title"), sortable: true }, + { columnKey: 'type', value: T.translate("event_list.type"), sortable: true, editableField: (extraProps) => (), render: (field) => field?.name, }, + { columnKey: 'title', value: T.translate("event_list.title"), sortable: true, editableField: true }, { columnKey: 'selection_status', value: T.translate("event_list.selection_status"), sortable: true } ]; @@ -593,7 +630,7 @@ class SummitEventListPage extends React.Component { const progress_flag_ddl = currentSummit.presentation_action_types.map(pf => ({value: pf.id, label: pf.label})) - let showColumns = fieldNames + let showColumns = fieldNames(selection_plans_ddl, track_ddl) .filter((f) => this.state.selectedColumns.includes(f.columnKey) && !defaultColumns.includes(f.columnKey)) .map((f2) => { let c = { @@ -607,6 +644,8 @@ class SummitEventListPage extends React.Component { if (f2.hasOwnProperty("render")) c = { ...c, render: f2.render }; + if (f2.hasOwnProperty("editableField")) c = { ...c, editableField: f2.editableField }; + return c; }); @@ -1067,15 +1106,15 @@ class SummitEventListPage extends React.Component { {events.length > 0 && (
-