From b5390c311cb59528a2b4e82903fce255ac9e745c Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Tue, 20 Aug 2024 20:04:51 -0700 Subject: [PATCH 01/33] Duplicate "Manage Schedule" tab as "Manage Venues" both point to the same react component --- app/controllers/competitions_controller.rb | 5 ++++ app/views/competitions/_nav.html.erb | 1 + app/views/competitions/edit_venues.html.erb | 28 +++++++++++++++++++++ config/locales/en.yml | 1 + config/routes.rb | 1 + 5 files changed, 36 insertions(+) create mode 100644 app/views/competitions/edit_venues.html.erb diff --git a/app/controllers/competitions_controller.rb b/app/controllers/competitions_controller.rb index cf16da3cb0b..c04c03e43f4 100644 --- a/app/controllers/competitions_controller.rb +++ b/app/controllers/competitions_controller.rb @@ -38,6 +38,7 @@ class CompetitionsController < ApplicationController before_action -> { redirect_to_root_unless_user(:can_manage_competition?, competition_from_params) }, only: [ :edit, :edit_events, + :edit_venues, :edit_schedule, :payment_integration_setup, ] @@ -253,6 +254,10 @@ def edit_events @competition = competition_from_params(includes: associations) end + def edit_venues + @competition = competition_from_params(includes: [competition_events: { rounds: { competition_event: [:event] } }, competition_venues: { venue_rooms: { schedule_activities: [:child_activities] } }]) + end + def edit_schedule @competition = competition_from_params(includes: [competition_events: { rounds: { competition_event: [:event] } }, competition_venues: { venue_rooms: { schedule_activities: [:child_activities] } }]) end diff --git a/app/views/competitions/_nav.html.erb b/app/views/competitions/_nav.html.erb index 6c169113269..7a136c5563f 100644 --- a/app/views/competitions/_nav.html.erb +++ b/app/views/competitions/_nav.html.erb @@ -42,6 +42,7 @@ children: [ { text: t('.menu.orga_view'), path: edit_competition_path(@competition), icon: "lock" }, { text: t('.menu.event_view'), path: edit_events_path(@competition), icon: "cubes" }, + { text: t('.menu.venue_view'), path: edit_venues_path(@competition), icon: "building" }, { text: t('.menu.schedule_view'), path: edit_schedule_path(@competition), icon: "calendar" }, { text: t('.menu.payment_view'), path: competition_payment_integration_setup_path(@competition), icon: "money bill alternate" }, ] + diff --git a/app/views/competitions/edit_venues.html.erb b/app/views/competitions/edit_venues.html.erb new file mode 100644 index 00000000000..63393f753f6 --- /dev/null +++ b/app/views/competitions/edit_venues.html.erb @@ -0,0 +1,28 @@ +<% provide(:title, "Manage venue for #{@competition.name}") %> +<% add_to_packs("wca_maps") %> + +<%= render layout: 'nav' do %> + <% if !@competition.has_defined_dates? %> +
+ There is no start and/or end date assigned to this competition yet. + Before you can manage your venues, you have to add them + <%= link_to "here", edit_competition_path(@competition, anchor: "competition_start_date") %>. +
+ <% elsif @competition.country.blank? %> +
+ There is no country assigned to this competition yet. + Before you can manage your venues, you have to add one + <%= link_to "here", edit_competition_path(@competition, anchor: "competition_countryId") %>. +
+ <% else %> + <%= react_component("EditSchedule", { + competitionId: @competition.id, + wcifEvents: @competition.events_wcif, + wcifSchedule: @competition.schedule_wcif, + countryZones: @competition.country_zones, + calendarLocale: I18n.locale, + }, { + id: 'edit-schedule-area' + }) %> + <% end %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 3eb4823151c..ff4543e7af0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2086,6 +2086,7 @@ en: clone: "Clone" orga_view: "Organizer view" event_view: "Manage events" + venue_view: "Manage venues" schedule_view: "Manage schedule" payment_view: "Setup payments" admin_view: "Admin view" diff --git a/config/routes.rb b/config/routes.rb index 1a6a7bf3225..c70d2e9dbff 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -138,6 +138,7 @@ # Stripe needs this special redirect URL during OAuth, see the linked controller method for details get 'stripe-connect' => 'competitions#stripe_connect', as: :competitions_stripe_connect get 'competitions/:id/events/edit' => 'competitions#edit_events', as: :edit_events + get 'competitions/:id/venues/edit' => 'competitions#edit_venues', as: :edit_venues get 'competitions/:id/schedule/edit' => 'competitions#edit_schedule', as: :edit_schedule get 'competitions/edit/nearby_competitions' => 'competitions#nearby_competitions', as: :nearby_competitions get 'competitions/edit/series_eligible_competitions' => 'competitions#series_eligible_competitions', as: :series_eligible_competitions From 2e8c982cc840b4b6555e94ce252cdd9637f31f73 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 15:23:06 -0700 Subject: [PATCH 02/33] Duplicate EditSchedule forlder as EditVenues --- .../EditActivities/ActionsHeader.js | 129 +++++ .../EditActivities/ActivityPicker.js | 106 ++++ .../EditActivities/EditActivityModal.js | 122 +++++ .../EditVenues/EditActivities/index.js | 507 ++++++++++++++++++ .../EditVenues/EditVenues/RoomPanel.js | 81 +++ .../EditVenues/EditVenues/VenueLocationMap.js | 128 +++++ .../EditVenues/EditVenues/VenuePanel.js | 184 +++++++ .../components/EditVenues/EditVenues/index.js | 51 ++ app/webpacker/components/EditVenues/index.js | 188 +++++++ .../components/EditVenues/store/actions.js | 219 ++++++++ .../components/EditVenues/store/reducer.js | 318 +++++++++++ app/webpacker/components/EditVenues/utils.js | 106 ++++ 12 files changed, 2139 insertions(+) create mode 100644 app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js create mode 100644 app/webpacker/components/EditVenues/EditActivities/ActivityPicker.js create mode 100644 app/webpacker/components/EditVenues/EditActivities/EditActivityModal.js create mode 100644 app/webpacker/components/EditVenues/EditActivities/index.js create mode 100644 app/webpacker/components/EditVenues/EditVenues/RoomPanel.js create mode 100644 app/webpacker/components/EditVenues/EditVenues/VenueLocationMap.js create mode 100644 app/webpacker/components/EditVenues/EditVenues/VenuePanel.js create mode 100644 app/webpacker/components/EditVenues/EditVenues/index.js create mode 100644 app/webpacker/components/EditVenues/index.js create mode 100644 app/webpacker/components/EditVenues/store/actions.js create mode 100644 app/webpacker/components/EditVenues/store/reducer.js create mode 100644 app/webpacker/components/EditVenues/utils.js diff --git a/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js b/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js new file mode 100644 index 00000000000..b5ea6cd3371 --- /dev/null +++ b/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; +import { + Button, + Checkbox, + Container, + Form, + Icon, + Modal, +} from 'semantic-ui-react'; + +import { copyRoomActivities } from '../store/actions'; +import { useDispatch, useStore } from '../../../lib/providers/StoreProvider'; +import { useConfirm } from '../../../lib/providers/ConfirmProvider'; +import { venueWcifFromRoomId } from '../../../lib/utils/wcif'; +import useInputState from '../../../lib/hooks/useInputState'; + +function ActionsHeader({ + selectedRoomId, + shouldUpdateMatches, + setShouldUpdateMatches, +}) { + const { wcifSchedule } = useStore(); + + const [isCopyModalOpen, setIsCopyModalOpen] = useState(false); + + const otherRoomsWithNonEmptySchedules = wcifSchedule.venues.flatMap( + (venue) => venue.rooms.filter( + (room) => room.activities.length > 0 && room.id !== selectedRoomId, + ).map((room) => ({ + key: room.id, + text: `${venue.name} - ${room.name}`, + value: room.id, + })), + ); + + return ( + otherRoomsWithNonEmptySchedules.length > 0 && ( + + setIsCopyModalOpen(false)} + /> + + + + + ) + ); +} + +function CopyRoomScheduleModal({ + isOpen, + selectedRoomId, + roomOptions, + close, +}) { + const { wcifSchedule } = useStore(); + const dispatch = useDispatch(); + const confirm = useConfirm(); + + const [toCopyRoomId, setToCopyRoomId] = useInputState(); + const selectedRoomVenue = venueWcifFromRoomId(wcifSchedule, selectedRoomId); + const toCopyRoomVenue = venueWcifFromRoomId(wcifSchedule, toCopyRoomId); + const areRoomsInSameVenue = selectedRoomVenue.id === toCopyRoomVenue?.id; + + const onClose = () => { + setToCopyRoomId(undefined); + close(); + }; + + const dispatchAndClose = () => { + dispatch(copyRoomActivities(toCopyRoomId, selectedRoomId)); + onClose(); + }; + + const handleCopyRoom = () => { + if (areRoomsInSameVenue) { + dispatchAndClose(); + } else { + confirm({ + content: 'The room you selected is in a different venue. You should probably only be copying from a different venue for a multi-location fewest moves competition. If so, make sure you correctly set all venue time zones BEFORE proceeding with this copy. Are you sure you want to proceed?', + }).then(dispatchAndClose); + } + }; + + return ( + + Copy Existing Schedule + + + + + + )} + /> + + + + )} + /> + + +
+ + + +
+ + + ); +} + +export default RoomPanel; diff --git a/app/webpacker/components/EditVenues/EditVenues/VenueLocationMap.js b/app/webpacker/components/EditVenues/EditVenues/VenueLocationMap.js new file mode 100644 index 00000000000..4bfdaffd767 --- /dev/null +++ b/app/webpacker/components/EditVenues/EditVenues/VenueLocationMap.js @@ -0,0 +1,128 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { + MapContainer, + Marker, + Popup, + TileLayer, + useMap, +} from 'react-leaflet'; +import { toDegrees, toMicrodegrees } from '../../../lib/utils/edit-schedule'; +import { userTileProvider } from '../../../lib/leaflet-wca/providers'; +import { useDispatch } from '../../../lib/providers/StoreProvider'; +import { editVenue } from '../store/actions'; +import ResizeMapIFrame from '../../../lib/utils/leaflet-iframe'; + +function GeoSearchControl({ + onGeoSearchResult, +}) { + const map = useMap(); + + useEffect(() => { + const searchControl = window.wca.createSearchInput(map); + + map.on('geosearch/showlocation', onGeoSearchResult); + map.zoomControl.setPosition('bottomright'); + + return () => { + map.removeControl(searchControl); + }; + }, [map, onGeoSearchResult]); + + return null; +} + +export function DraggableMarker({ + position, + setPosition, + disabled = false, + markerRef = null, + children, +}) { + const map = useMap(); + + const updatePosition = useCallback((e) => setPosition(e, e.target.getLatLng()), [setPosition]); + + useEffect(() => { + map.panTo(position); + }, [map, position]); + + return ( + + {children} + + ); +} + +function VenueLocationMap({ + venue, +}) { + const dispatch = useDispatch(); + const markerRef = useRef(); + + const [searchResultPopup, setSearchResultPopup] = useState(); + + const markerPopup = useMemo(() => { + if (searchResultPopup) { + return {searchResultPopup}; + } + + return null; + }, [searchResultPopup]); + + const venuePosition = useMemo(() => ({ + lat: toDegrees(venue.latitudeMicrodegrees), + lng: toDegrees(venue.longitudeMicrodegrees), + }), [venue.latitudeMicrodegrees, venue.longitudeMicrodegrees]); + + const setVenuePosition = useCallback((evt, { lat, lng }) => { + dispatch(editVenue(venue.id, 'latitudeMicrodegrees', toMicrodegrees(lat))); + dispatch(editVenue(venue.id, 'longitudeMicrodegrees', toMicrodegrees(lng))); + }, [dispatch, venue.id]); + + const provider = userTileProvider; + + const onGeoSearchResult = useCallback((evt) => { + setVenuePosition(evt, { + lat: evt.location.y, + lng: evt.location.x, + }); + + setSearchResultPopup(evt.location.label); + }, [setVenuePosition, setSearchResultPopup]); + + return ( + + + + + + {markerPopup} + + + ); +} + +export default VenueLocationMap; diff --git a/app/webpacker/components/EditVenues/EditVenues/VenuePanel.js b/app/webpacker/components/EditVenues/EditVenues/VenuePanel.js new file mode 100644 index 00000000000..cc153c561e8 --- /dev/null +++ b/app/webpacker/components/EditVenues/EditVenues/VenuePanel.js @@ -0,0 +1,184 @@ +import React, { useCallback, useMemo } from 'react'; +import { + Button, + Card, + Container, + DropdownHeader, + Form, + Icon, + Image, +} from 'semantic-ui-react'; +import _ from 'lodash'; + +import VenueLocationMap from './VenueLocationMap'; +import { countries, backendTimezones } from '../../../lib/wca-data.js.erb'; +import RoomPanel from './RoomPanel'; +import { useDispatch } from '../../../lib/providers/StoreProvider'; +import { useConfirm } from '../../../lib/providers/ConfirmProvider'; +import { + addRoom, + editVenue, + removeVenue, +} from '../store/actions'; +import { toDegrees, toMicrodegrees } from '../../../lib/utils/edit-schedule'; +import { getTimeZoneDropdownLabel, sortByOffset } from '../../../lib/utils/timezone'; + +const countryOptions = countries.real.map((country) => ({ + key: country.iso2, + text: country.name, + value: country.iso2, + flag: country.iso2.toLowerCase(), +})); + +function VenuePanel({ + venue, + countryZones, + referenceTime, +}) { + const dispatch = useDispatch(); + const confirm = useConfirm(); + + const handleCoordinateChange = (evt, { name, value }) => { + dispatch(editVenue(venue.id, name, toMicrodegrees(value))); + }; + + const handleVenueChange = (evt, { name, value }) => { + dispatch(editVenue(venue.id, name, value)); + }; + + const handleDeleteVenue = () => { + confirm({ + content: `Are you sure you want to delete the venue ${venue.name}? This will also delete all associated rooms and all associated schedules. THIS ACTION CANNOT BE UNDONE!`, + }).then(() => dispatch(removeVenue(venue.id))); + }; + + const handleAddRoom = () => { + dispatch(addRoom(venue.id)); + }; + + const getVenueTzDropdownLabel = useCallback( + // The whole page is not localized yet, so we just hard-code US English here as well. + (tzId) => getTimeZoneDropdownLabel(tzId, referenceTime, 'en-US'), + [referenceTime], + ); + + const makeTimeZoneOption = useCallback((key) => ({ + key, + text: getVenueTzDropdownLabel(key), + value: key, + }), [getVenueTzDropdownLabel]); + + // Instead of giving *all* TZInfo, use uniq-fied rails "meaningful" subset + // We'll add the "country_zones" to that, because some of our competitions + // use TZs not included in this subset. + // We want to display the "country_zones" first, so that it's more convenient for the user. + // In the end the array should look like that: + // - country_zone_a, country_zone_b, [...], other_tz_a, other_tz_b, [...] + const timezoneOptions = useMemo(() => { + // Stuff that is recommended based on the country list + const competitionZoneIds = _.uniq(countryZones); + const sortedCompetitionZones = sortByOffset(competitionZoneIds, referenceTime); + + // Stuff that is listed in our `backendTimezones` list but not in the preferred country list + const otherZoneIds = _.difference(backendTimezones, competitionZoneIds); + const sortedOtherZones = sortByOffset(otherZoneIds, referenceTime); + + // Both merged together, with the countryZone entries listed first. + return [ + { + as: DropdownHeader, + key: 'local-zones-header', + text: 'Local time zones', + disabled: true, + }, + ...sortedCompetitionZones.map(makeTimeZoneOption), + { + as: DropdownHeader, + key: 'other-zones-header', + text: 'Other time zones', + disabled: true, + }, + ...sortedOtherZones.map(makeTimeZoneOption), + ]; + }, [countryZones, referenceTime, makeTimeZoneOption]); + + return ( + + { /* Needs the className 'image' so that SemUI fills the top of the card */ } + + + + + + + + +
+ + + + + + + + +
+
+ + + + Rooms + + + + {venue.rooms.map((room) => ( + + ))} + + + +
+ ); +} + +export default VenuePanel; diff --git a/app/webpacker/components/EditVenues/EditVenues/index.js b/app/webpacker/components/EditVenues/EditVenues/index.js new file mode 100644 index 00000000000..5fba7373db4 --- /dev/null +++ b/app/webpacker/components/EditVenues/EditVenues/index.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { + Button, + Card, + Container, + Icon, + Segment, +} from 'semantic-ui-react'; +import { useDispatch, useStore } from '../../../lib/providers/StoreProvider'; +import VenuePanel from './VenuePanel'; +import { addVenue } from '../store/actions'; + +function EditVenues({ + countryZones, + referenceTime, +}) { + const { wcifSchedule } = useStore(); + + const dispatch = useDispatch(); + + const handleAddVenue = () => { + dispatch(addVenue()); + }; + + return ( +
+ + + Please add all your venues and rooms below: + + + + + {wcifSchedule.venues.map((venue) => ( + + ))} + + +
+ ); +} + +export default EditVenues; diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js new file mode 100644 index 00000000000..8c4cfbfe15f --- /dev/null +++ b/app/webpacker/components/EditVenues/index.js @@ -0,0 +1,188 @@ +import React, { + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; + +import { + Accordion, + Button, + Container, + Message, +} from 'semantic-ui-react'; + +import _ from 'lodash'; + +import { useSaveWcifAction } from '../../lib/utils/wcif'; +import { changesSaved } from './store/actions'; +import wcifScheduleReducer from './store/reducer'; +import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider'; +import ConfirmProvider from '../../lib/providers/ConfirmProvider'; +import EditVenues from './EditVenues'; +import EditActivities from './EditActivities'; + +function EditSchedule({ + wcifEvents, + countryZones, + referenceTime, + calendarLocale, +}) { + const { + competitionId, + wcifSchedule, + initialWcifSchedule, + } = useStore(); + + const dispatch = useDispatch(); + + const [openAccordion, setOpenAccordion] = useState(-1); + + const unsavedChanges = useMemo(() => ( + !_.isEqual(wcifSchedule, initialWcifSchedule) + ), [wcifSchedule, initialWcifSchedule]); + + const onUnload = useCallback((e) => { + // Prompt the user before letting them navigate away from this page with unsaved changes. + if (unsavedChanges) { + const confirmationMessage = 'You have unsaved changes, are you sure you want to leave?'; + e.returnValue = confirmationMessage; + return confirmationMessage; + } + + return null; + }, [unsavedChanges]); + + useEffect(() => { + window.addEventListener('beforeunload', onUnload); + + return () => { + window.removeEventListener('beforeunload', onUnload); + }; + }, [onUnload]); + + const { saveWcif, saving } = useSaveWcifAction(); + + const save = useCallback(() => { + saveWcif( + competitionId, + { schedule: wcifSchedule }, + () => dispatch(changesSaved()), + ); + }, [competitionId, dispatch, saveWcif, wcifSchedule]); + + const renderUnsavedChangesAlert = () => ( + + You have unsaved changes. Don't forget to + {' '} + + + ); + + const renderIntroductionMessage = () => ( + +

+ Depending on the size and setup of the competition, it may take place in + several rooms of several venues. + Therefore a schedule is necessarily linked to a specific room. + Each room may have its own schedule (with all or a subset of events). + So you can start creating the competition’s schedule below by adding at + least one venue with one room. + Then you will be able to select this room in the "Edit schedules" + panel, and drag and drop event rounds (or attempts for some events) on it. +

+

+ For the typical simple competition, creating one "Main venue" + with one "Main room" is enough. + If your competition has a single venue but multiple "stages" with different + schedules, please input them as different rooms. +

+
+ ); + + const handleAccordionClick = (evt, titleProps) => { + const { index } = titleProps; + const newIndex = openAccordion === index ? -1 : index; + + setOpenAccordion(newIndex); + }; + + return ( + <> + {renderIntroductionMessage()} +
+ {unsavedChanges && renderUnsavedChangesAlert()} + + + Edit venues information + + + + + + Edit schedules + + + + + + {unsavedChanges && renderUnsavedChangesAlert()} +
+ + ); +} + +export default function Wrapper({ + competitionId, + wcifEvents, + wcifSchedule, + countryZones, + referenceTime, + calendarLocale, +}) { + return ( + + + + + + ); +} diff --git a/app/webpacker/components/EditVenues/store/actions.js b/app/webpacker/components/EditVenues/store/actions.js new file mode 100644 index 00000000000..660f946ba89 --- /dev/null +++ b/app/webpacker/components/EditVenues/store/actions.js @@ -0,0 +1,219 @@ +export const ChangesSaved = 'saving_started'; +export const AddActivity = 'ADD_ACTIVITY'; +export const EditActivity = 'EDIT_ACTIVITY'; +export const RemoveActivity = 'REMOVE_ACTIVITY'; +export const MoveActivity = 'MOVE_ACTIVITY'; +export const ScaleActivity = 'SCALE_ACTIVITY'; +export const EditVenue = 'EDIT_VENUE'; +export const EditRoom = 'EDIT_ROOM'; +export const RemoveVenue = 'REMOVE_VENUE'; +export const RemoveRoom = 'REMOVE_ROOM'; +export const AddVenue = 'ADD_VENUE'; +export const AddRoom = 'ADD_ROOM'; +export const CopyVenue = 'COPY_VENUE'; +export const CopyRoom = 'COPY_ROOM'; +export const CopyRoomActivities = 'COPY_ROOM_ACTIVITIES'; + +/** + * Action creator for marking changes as saved. + * @returns {Action} + */ +export const changesSaved = () => ({ + type: ChangesSaved, +}); + +/** + * Action creator for adding activity. + * @param {Activity} wcifActivity + * @param {int} roomId + * @returns {Action} + */ +export const addActivity = (wcifActivity, roomId) => ({ + type: AddActivity, + payload: { + wcifActivity, + roomId, + }, +}); + +/** + * Action creator for modifying details of an activity. + * @param {int} activityId + * @param {string} key + * @param {string} value + * @param {boolean} updateMatches + * @returns {Action} + */ +export const editActivity = (activityId, key, value, updateMatches) => ({ + type: EditActivity, + payload: { + activityId, + key, + value, + updateMatches, + }, +}); + +/** + * Action creator for removing activity. + * @param {int} activityId + * @param {boolean} updateMatches + * @returns {Action} + */ +export const removeActivity = (activityId, updateMatches) => ({ + type: RemoveActivity, + payload: { + activityId, + updateMatches, + }, +}); + +/** + * Action creator for moving an activity's time. + * @param {int} activityId + * @param {string} isoDuration + * @param {boolean} updateMatches + * @returns {Action} + */ +export const moveActivity = (activityId, isoDuration, updateMatches = false) => ({ + type: MoveActivity, + payload: { + activityId, + isoDuration, + updateMatches, + }, +}); + +/** + * Action creator for scaling an activity's time, + * i.e. changing the start and/or end date by some delta. + * @param {int} activityId + * @param {string} isoDeltaStart + * @param {string} isoDeltaEnd + * @param {boolean} updateMatches + * @returns {Action} + */ +export const scaleActivity = (activityId, isoDeltaStart, isoDeltaEnd, updateMatches = false) => ({ + type: ScaleActivity, + payload: { + activityId, + isoDeltaStart, + isoDeltaEnd, + updateMatches, + }, +}); + +/** + * Action creator for changing a venue's properties. + * @param {int} venueId + * @param {string} propertyKey + * @param {string} newProperty + * @returns {Action} + */ +export const editVenue = (venueId, propertyKey, newProperty) => ({ + type: EditVenue, + payload: { + venueId, + propertyKey, + newProperty, + }, +}); + +/** + * Action creator for changing a room's properties. + * @param {int} roomId + * @param {string} propertyKey + * @param {string} newProperty + * @returns {Action} + */ +export const editRoom = (roomId, propertyKey, newProperty) => ({ + type: EditRoom, + payload: { + roomId, + propertyKey, + newProperty, + }, +}); + +/** + * Action creator for removing a venue. + * @param {int} venueId + * @returns {Action} + */ +export const removeVenue = (venueId) => ({ + type: RemoveVenue, + payload: { + venueId, + }, +}); + +/** + * Action creator for removing a room. + * @param {int} roomId + * @returns {Action} + */ +export const removeRoom = (roomId) => ({ + type: RemoveRoom, + payload: { + roomId, + }, +}); + +/** + * Action creator for adding a blank venue. + * @returns {Action} + */ +export const addVenue = () => ({ + type: AddVenue, + payload: {}, +}); + +/** + * Action creator for adding a blank room. + * @param {int} venueId + * @returns {Action} + */ +export const addRoom = (venueId) => ({ + type: AddRoom, + payload: { + venueId, + }, +}); + +/** + * Action creator for copying a venue. + * @param {int} venueId + * @returns {Action} + */ +export const copyVenue = (venueId) => ({ + type: CopyVenue, + payload: { + venueId, + }, +}); + +/** + * Action creator for copying a room. + * @param {int} roomId + * @returns {Action} + */ +export const copyRoom = (roomId) => ({ + type: CopyRoom, + payload: { + roomId, + }, +}); + +/** + * Action creator for copying a room's activities to another room. + * @param {int} sourceRoomId + * @param {int} targetRoomId + * @returns {Action} + */ +export const copyRoomActivities = (sourceRoomId, targetRoomId) => ({ + type: CopyRoomActivities, + payload: { + sourceRoomId, + targetRoomId, + }, +}); diff --git a/app/webpacker/components/EditVenues/store/reducer.js b/app/webpacker/components/EditVenues/store/reducer.js new file mode 100644 index 00000000000..f9ba3dcb17f --- /dev/null +++ b/app/webpacker/components/EditVenues/store/reducer.js @@ -0,0 +1,318 @@ +import { + AddActivity, + AddRoom, + AddVenue, + ChangesSaved, + CopyRoom, + CopyRoomActivities, + CopyVenue, + EditActivity, + EditRoom, + EditVenue, + MoveActivity, + RemoveActivity, + RemoveRoom, + RemoveVenue, + ScaleActivity, +} from './actions'; +import { + copyActivity, copyRoom, copyVenue, nextActivityId, nextRoomId, nextVenueId, +} from '../../../lib/utils/edit-schedule'; +import { + changeActivityTimezone, moveActivityByDuration, scaleActivitiesByDuration, +} from '../utils'; +import { + activityWcifFromId, + doActivitiesMatch, + roomWcifFromId, + venueWcifFromRoomId, +} from '../../../lib/utils/wcif'; +import { defaultRoomColor } from '../../../lib/wca-data.js.erb'; + +const reducers = { + [ChangesSaved]: (state) => ({ + ...state, + initialWcifSchedule: state.wcifSchedule, + }), + + [AddActivity]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => (room.id === payload.roomId ? ({ + ...room, + activities: [ + ...room.activities, + { + ...payload.wcifActivity, + id: nextActivityId(state.wcifSchedule), + }, + ], + }) : room)), + })), + }, + }), + + [EditActivity]: (state, { payload }) => { + const selectedActivity = activityWcifFromId(state.wcifSchedule, payload.activityId); + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => ({ + ...room, + activities: room.activities.map((activity) => ( + (activity.id === selectedActivity.id || ( + payload.updateMatches && doActivitiesMatch(activity, selectedActivity) + )) + ? { ...activity, [payload.key]: payload.value } + : activity + )), + })), + })), + }, + }; + }, + + [RemoveActivity]: (state, { payload }) => { + const selectedActivity = activityWcifFromId(state.wcifSchedule, payload.activityId); + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => ({ + ...room, + activities: room.activities.filter((activity) => ( + activity.id !== payload.activityId && ( + !payload.updateMatches || !doActivitiesMatch(activity, selectedActivity) + ) + )), + })), + })), + }, + }; + }, + + [MoveActivity]: (state, { payload }) => { + const selectedActivity = activityWcifFromId(state.wcifSchedule, payload.activityId); + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => ({ + ...room, + activities: room.activities.map((activity) => ( + (activity.id === selectedActivity.id || ( + payload.updateMatches && doActivitiesMatch(activity, selectedActivity) + )) + ? moveActivityByDuration(activity, payload.isoDuration) + : activity + )), + })), + })), + }, + }; + }, + + [ScaleActivity]: (state, { payload }) => { + const selectedActivity = activityWcifFromId(state.wcifSchedule, payload.activityId); + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => ({ + ...room, + activities: room.activities.map((activity) => ( + (activity.id === selectedActivity.id || ( + payload.updateMatches && doActivitiesMatch(activity, selectedActivity) + )) + ? scaleActivitiesByDuration(activity, payload.isoDeltaStart, payload.isoDeltaEnd) + : activity + )), + })), + })), + }, + }; + }, + + [EditVenue]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => (venue.id === payload.venueId ? { + ...venue, + [payload.propertyKey]: payload.newProperty, + rooms: payload.propertyKey === 'timezone' + ? venue.rooms.map((room) => ({ + ...room, + activities: room.activities.map((activity) => ( + changeActivityTimezone( + activity, + venue.timezone, + payload.newProperty, + ) + )), + })) + : venue.rooms, + } : venue)), + }, + }), + + [EditRoom]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.map((room) => (room.id === payload.roomId ? { + ...room, + [payload.propertyKey]: payload.newProperty, + } : room)), + })), + }, + }), + + [RemoveVenue]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.filter((venue) => venue.id !== payload.venueId), + }, + }), + + [RemoveRoom]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => ({ + ...venue, + rooms: venue.rooms.filter((room) => room.id !== payload.roomId), + })), + }, + }), + + [AddVenue]: (state) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: [ + ...state.wcifSchedule.venues, + { + id: nextVenueId(state.wcifSchedule), + latitudeMicrodegrees: 0, + longitudeMicrodegrees: 0, + rooms: [], + extensions: [], + }, + ], + }, + }), + + [AddRoom]: (state, { payload }) => ({ + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => (venue.id === payload.venueId ? { + ...venue, + rooms: [ + ...venue.rooms, + { + id: nextRoomId(state.wcifSchedule), + color: defaultRoomColor, + activities: [], + extensions: [], + }, + ], + } : venue)), + }, + }), + + [CopyVenue]: (state, { payload }) => { + const venue = state.wcifSchedule.venues.find(({ id }) => id === payload.venueId); + if (!venue) return state; + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: [ + ...state.wcifSchedule.venues, + { + ...copyVenue(state.wcifSchedule, venue), + name: `Copy of ${venue.name}`, + }, + ], + }, + }; + }, + + [CopyRoom]: (state, { payload }) => { + const targetVenue = venueWcifFromRoomId(state.wcifSchedule, payload.roomId); + if (!targetVenue) return state; + const room = targetVenue.rooms.find(({ id }) => id === payload.roomId); + if (!room) return state; + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => (venue.id === targetVenue.id ? { + ...venue, + rooms: [ + ...venue.rooms, + { + ...copyRoom(state.wcifSchedule, room), + name: `Copy of ${room.name}`, + }, + ], + } : venue)), + }, + }; + }, + + [CopyRoomActivities]: (state, { payload }) => { + const { sourceRoomId, targetRoomId } = payload; + const sourceRoomActivities = roomWcifFromId(state.wcifSchedule, sourceRoomId).activities; + if (sourceRoomActivities.length === 0) return state; + const copiedActivities = sourceRoomActivities.map( + (activity) => copyActivity(state.wcifSchedule, activity), + ); + const targetRoomVenueId = venueWcifFromRoomId(state.wcifSchedule, targetRoomId).id; + + return { + ...state, + wcifSchedule: { + ...state.wcifSchedule, + venues: state.wcifSchedule.venues.map((venue) => (venue.id === targetRoomVenueId ? { + ...venue, + rooms: venue.rooms.map((room) => (room.id === targetRoomId ? { + ...room, + activities: [...room.activities, ...copiedActivities], + } : room)), + } : venue)), + }, + }; + }, +}; + +export default function rootReducer(state, action) { + const reducer = reducers[action.type]; + if (reducer) { + return reducer(state, action); + } + return state; +} diff --git a/app/webpacker/components/EditVenues/utils.js b/app/webpacker/components/EditVenues/utils.js new file mode 100644 index 00000000000..6495a75f474 --- /dev/null +++ b/app/webpacker/components/EditVenues/utils.js @@ -0,0 +1,106 @@ +import { + addIsoDurations, + changeTimezoneKeepingLocalTime, + millisecondsBetween, + moveByIsoDuration, + rescaleIsoDuration, +} from '../../lib/utils/edit-schedule'; + +export const moveActivityByDuration = (activity, isoDuration) => ({ + ...activity, + startTime: moveByIsoDuration(activity.startTime, isoDuration), + endTime: moveByIsoDuration(activity.endTime, isoDuration), + childActivities: activity.childActivities.map((childActivity) => ( + moveActivityByDuration(childActivity, isoDuration) + )), +}); + +export const scaleActivitiesByDuration = (activity, isoDeltaStart, isoDeltaEnd) => { + const rootActivityLengthMs = millisecondsBetween( + activity.startTime, + activity.endTime, + ); + + return ({ + ...activity, + startTime: moveByIsoDuration(activity.startTime, isoDeltaStart), + endTime: moveByIsoDuration(activity.endTime, isoDeltaEnd), + childActivities: activity.childActivities.map((childActivity) => { + // Unfortunately, scaling child activities (properly) is rocket science. + const childActivityLengthMs = millisecondsBetween( + childActivity.startTime, + childActivity.endTime, + ); + + // Say you scale the start by -1 hour (i.e. 1 hour earlier). + // The amount that you have to scale a child by is directly proportional + // to the child's length. So we calculate the proportion of durations using milliseconds. + // Say you have a parent activity with three equally sized children. In that case, + // every child gets scaled down an equal amount, because they are all equally long. + // If you plan your schedule with one slow group (long duration) and one fast group + // (short duration), the fast and short group only needs to be rescaled a little bit + // while the slow and long group gets the "lion share" of the scaling factor. + const scalingFactor = childActivityLengthMs / rootActivityLengthMs; + + const childStartScale = rescaleIsoDuration(isoDeltaStart, scalingFactor); + const childEndScale = rescaleIsoDuration(isoDeltaEnd, scalingFactor); + + // Of course, this all has to happen recursively because children can have children! + const scaledChild = scaleActivitiesByDuration( + childActivity, + childStartScale, + childEndScale, + ); + + // However, it doesn't end there. When a parent activity _scales_, + // the child activities also have to _move_. Think of an activity "shrinking", + // i.e. becoming shorter: Then the children also "shrink" as a result. + // This "shrinking" will create gaps which can only be filled by the children + // _moving_ closer together after shrinking down. + + const ownStartToParentStart = millisecondsBetween( + childActivity.startTime, + activity.startTime, + ); + + const ownEndToParentEnd = millisecondsBetween( + childActivity.endTime, + activity.endTime, + ); + + // Again, this growth is proportional to the size of the child activity. + const scalingStartUp = ownStartToParentStart / rootActivityLengthMs; + const scalingEndDown = ownEndToParentEnd / rootActivityLengthMs; + + // Now it gets a little bit crazy: + // - When applying a Delta to the END of the activity, we have to move it UP + // - When applying a Delta to the START of the activity, we have to move it DOWN + // Think of it this way: With two subsequent child activities, removing a few minutes + // from the END of either activity creates a gap that needs to be closed by moving + // the second, later activity UP closer towards its predecessor. + // The same logic applies in reverse for adding minutes instead of removing minutes. + const moveUpwardsDuration = rescaleIsoDuration(isoDeltaEnd, scalingStartUp); + const moveDownwardsDuration = rescaleIsoDuration(isoDeltaStart, scalingEndDown); + + // Both directional Deltas are added together, and it is possible that they cancel + // each other out, most notably if DeltaStart == -DeltaEnd. + const totalMovingDuration = addIsoDurations(moveUpwardsDuration, moveDownwardsDuration); + + // Phew, we're done. + return moveActivityByDuration(scaledChild, totalMovingDuration); + }), + }); +}; + +export const changeActivityTimezone = (activity, oldTimezone, newTimezone) => ({ + ...activity, + startTime: changeTimezoneKeepingLocalTime(activity.startTime, oldTimezone, newTimezone), + endTime: changeTimezoneKeepingLocalTime(activity.endTime, oldTimezone, newTimezone), + childActivities: activity.childActivities.map((childActivity) => ( + changeActivityTimezone( + childActivity, + oldTimezone, + newTimezone, + ) + )), +}); From d5cbc916176bf1975a060a6b35273461fc5a84e4 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 15:48:42 -0700 Subject: [PATCH 03/33] Add missing referenceTime --- app/views/competitions/edit_venues.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/competitions/edit_venues.html.erb b/app/views/competitions/edit_venues.html.erb index 63393f753f6..06db92ee540 100644 --- a/app/views/competitions/edit_venues.html.erb +++ b/app/views/competitions/edit_venues.html.erb @@ -20,6 +20,7 @@ wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, countryZones: @competition.country_zones, + referenceTime: @competition.start_date.to_fs, calendarLocale: I18n.locale, }, { id: 'edit-schedule-area' From 2a16a61a0bfc892b7e123c73ce27fe6a976cbec9 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 15:49:19 -0700 Subject: [PATCH 04/33] Use new (copied) EditVenues component in new tab --- app/views/competitions/edit_venues.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/competitions/edit_venues.html.erb b/app/views/competitions/edit_venues.html.erb index 06db92ee540..77b651b7d32 100644 --- a/app/views/competitions/edit_venues.html.erb +++ b/app/views/competitions/edit_venues.html.erb @@ -15,7 +15,7 @@ <%= link_to "here", edit_competition_path(@competition, anchor: "competition_countryId") %>. <% else %> - <%= react_component("EditSchedule", { + <%= react_component("EditVenues", { competitionId: @competition.id, wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, From e5854acb11443838e1b2365887a4db6f19beaa0a Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 15:54:14 -0700 Subject: [PATCH 05/33] Remove schedule-related things from new EditVenues --- app/views/competitions/edit_venues.html.erb | 2 - app/webpacker/components/EditVenues/index.js | 61 +++----------------- 2 files changed, 7 insertions(+), 56 deletions(-) diff --git a/app/views/competitions/edit_venues.html.erb b/app/views/competitions/edit_venues.html.erb index 77b651b7d32..4ce9d1dcef2 100644 --- a/app/views/competitions/edit_venues.html.erb +++ b/app/views/competitions/edit_venues.html.erb @@ -17,11 +17,9 @@ <% else %> <%= react_component("EditVenues", { competitionId: @competition.id, - wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, countryZones: @competition.country_zones, referenceTime: @competition.start_date.to_fs, - calendarLocale: I18n.locale, }, { id: 'edit-schedule-area' }) %> diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js index 8c4cfbfe15f..c1b5b630318 100644 --- a/app/webpacker/components/EditVenues/index.js +++ b/app/webpacker/components/EditVenues/index.js @@ -2,11 +2,9 @@ import React, { useCallback, useEffect, useMemo, - useState, } from 'react'; import { - Accordion, Button, Container, Message, @@ -20,13 +18,10 @@ import wcifScheduleReducer from './store/reducer'; import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider'; import ConfirmProvider from '../../lib/providers/ConfirmProvider'; import EditVenues from './EditVenues'; -import EditActivities from './EditActivities'; function EditSchedule({ - wcifEvents, countryZones, referenceTime, - calendarLocale, }) { const { competitionId, @@ -36,8 +31,6 @@ function EditSchedule({ const dispatch = useDispatch(); - const [openAccordion, setOpenAccordion] = useState(-1); - const unsavedChanges = useMemo(() => ( !_.isEqual(wcifSchedule, initialWcifSchedule) ), [wcifSchedule, initialWcifSchedule]); @@ -93,9 +86,9 @@ function EditSchedule({ several rooms of several venues. Therefore a schedule is necessarily linked to a specific room. Each room may have its own schedule (with all or a subset of events). - So you can start creating the competition’s schedule below by adding at - least one venue with one room. - Then you will be able to select this room in the "Edit schedules" + To create the competition’s schedule, start by adding at + least one venue with one room below. + Then you will be able to select this room in the "Manage schedule" panel, and drag and drop event rounds (or attempts for some events) on it.

@@ -107,51 +100,15 @@ function EditSchedule({ ); - const handleAccordionClick = (evt, titleProps) => { - const { index } = titleProps; - const newIndex = openAccordion === index ? -1 : index; - - setOpenAccordion(newIndex); - }; - return ( <> {renderIntroductionMessage()}

{unsavedChanges && renderUnsavedChangesAlert()} - - - Edit venues information - - - - - - Edit schedules - - - - - + {unsavedChanges && renderUnsavedChangesAlert()}
@@ -160,11 +117,9 @@ function EditSchedule({ export default function Wrapper({ competitionId, - wcifEvents, wcifSchedule, countryZones, referenceTime, - calendarLocale, }) { return ( From c0a075a6c8876c218a527056f3134a9881566738 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 15:56:05 -0700 Subject: [PATCH 06/33] Remove unused EditActivities folder in EditVenues --- .../EditActivities/ActionsHeader.js | 129 ----- .../EditActivities/ActivityPicker.js | 106 ---- .../EditActivities/EditActivityModal.js | 122 ----- .../EditVenues/EditActivities/index.js | 507 ------------------ 4 files changed, 864 deletions(-) delete mode 100644 app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js delete mode 100644 app/webpacker/components/EditVenues/EditActivities/ActivityPicker.js delete mode 100644 app/webpacker/components/EditVenues/EditActivities/EditActivityModal.js delete mode 100644 app/webpacker/components/EditVenues/EditActivities/index.js diff --git a/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js b/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js deleted file mode 100644 index b5ea6cd3371..00000000000 --- a/app/webpacker/components/EditVenues/EditActivities/ActionsHeader.js +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useState } from 'react'; -import { - Button, - Checkbox, - Container, - Form, - Icon, - Modal, -} from 'semantic-ui-react'; - -import { copyRoomActivities } from '../store/actions'; -import { useDispatch, useStore } from '../../../lib/providers/StoreProvider'; -import { useConfirm } from '../../../lib/providers/ConfirmProvider'; -import { venueWcifFromRoomId } from '../../../lib/utils/wcif'; -import useInputState from '../../../lib/hooks/useInputState'; - -function ActionsHeader({ - selectedRoomId, - shouldUpdateMatches, - setShouldUpdateMatches, -}) { - const { wcifSchedule } = useStore(); - - const [isCopyModalOpen, setIsCopyModalOpen] = useState(false); - - const otherRoomsWithNonEmptySchedules = wcifSchedule.venues.flatMap( - (venue) => venue.rooms.filter( - (room) => room.activities.length > 0 && room.id !== selectedRoomId, - ).map((room) => ({ - key: room.id, - text: `${venue.name} - ${room.name}`, - value: room.id, - })), - ); - - return ( - otherRoomsWithNonEmptySchedules.length > 0 && ( - - setIsCopyModalOpen(false)} - /> - - - - - ) - ); -} - -function CopyRoomScheduleModal({ - isOpen, - selectedRoomId, - roomOptions, - close, -}) { - const { wcifSchedule } = useStore(); - const dispatch = useDispatch(); - const confirm = useConfirm(); - - const [toCopyRoomId, setToCopyRoomId] = useInputState(); - const selectedRoomVenue = venueWcifFromRoomId(wcifSchedule, selectedRoomId); - const toCopyRoomVenue = venueWcifFromRoomId(wcifSchedule, toCopyRoomId); - const areRoomsInSameVenue = selectedRoomVenue.id === toCopyRoomVenue?.id; - - const onClose = () => { - setToCopyRoomId(undefined); - close(); - }; - - const dispatchAndClose = () => { - dispatch(copyRoomActivities(toCopyRoomId, selectedRoomId)); - onClose(); - }; - - const handleCopyRoom = () => { - if (areRoomsInSameVenue) { - dispatchAndClose(); - } else { - confirm({ - content: 'The room you selected is in a different venue. You should probably only be copying from a different venue for a multi-location fewest moves competition. If so, make sure you correctly set all venue time zones BEFORE proceeding with this copy. Are you sure you want to proceed?', - }).then(dispatchAndClose); - } - }; - - return ( - - Copy Existing Schedule - - - - - - Please add all your venues and rooms below: +

Venues

From 52ba83ba2f2ddd1f3976bf03f87a69fff5454016 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:22:38 -0700 Subject: [PATCH 10/33] Remove EditVenues component from EditSchedule --- app/views/competitions/edit_schedule.html.erb | 1 - .../components/EditSchedule/index.js | 68 ++----------------- 2 files changed, 6 insertions(+), 63 deletions(-) diff --git a/app/views/competitions/edit_schedule.html.erb b/app/views/competitions/edit_schedule.html.erb index 4faab4c0a9e..c7eae9f91a4 100644 --- a/app/views/competitions/edit_schedule.html.erb +++ b/app/views/competitions/edit_schedule.html.erb @@ -19,7 +19,6 @@ competitionId: @competition.id, wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, - countryZones: @competition.country_zones, referenceTime: @competition.start_date.to_fs, calendarLocale: I18n.locale, }, { diff --git a/app/webpacker/components/EditSchedule/index.js b/app/webpacker/components/EditSchedule/index.js index 8c4cfbfe15f..00a426305dd 100644 --- a/app/webpacker/components/EditSchedule/index.js +++ b/app/webpacker/components/EditSchedule/index.js @@ -2,11 +2,9 @@ import React, { useCallback, useEffect, useMemo, - useState, } from 'react'; import { - Accordion, Button, Container, Message, @@ -19,12 +17,10 @@ import { changesSaved } from './store/actions'; import wcifScheduleReducer from './store/reducer'; import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider'; import ConfirmProvider from '../../lib/providers/ConfirmProvider'; -import EditVenues from './EditVenues'; import EditActivities from './EditActivities'; function EditSchedule({ wcifEvents, - countryZones, referenceTime, calendarLocale, }) { @@ -36,8 +32,6 @@ function EditSchedule({ const dispatch = useDispatch(); - const [openAccordion, setOpenAccordion] = useState(-1); - const unsavedChanges = useMemo(() => ( !_.isEqual(wcifSchedule, initialWcifSchedule) ), [wcifSchedule, initialWcifSchedule]); @@ -89,69 +83,21 @@ function EditSchedule({ const renderIntroductionMessage = () => (

- Depending on the size and setup of the competition, it may take place in - several rooms of several venues. - Therefore a schedule is necessarily linked to a specific room. - Each room may have its own schedule (with all or a subset of events). - So you can start creating the competition’s schedule below by adding at - least one venue with one room. - Then you will be able to select this room in the "Edit schedules" - panel, and drag and drop event rounds (or attempts for some events) on it. -

-

- For the typical simple competition, creating one "Main venue" - with one "Main room" is enough. - If your competition has a single venue but multiple "stages" with different - schedules, please input them as different rooms. + To create a schedule, first visit the "Manage venues" panel to add venues and rooms/stages.

); - const handleAccordionClick = (evt, titleProps) => { - const { index } = titleProps; - const newIndex = openAccordion === index ? -1 : index; - - setOpenAccordion(newIndex); - }; - return ( <> {renderIntroductionMessage()}
{unsavedChanges && renderUnsavedChangesAlert()} - - - Edit venues information - - - - - - Edit schedules - - - - - + {unsavedChanges && renderUnsavedChangesAlert()}
@@ -162,7 +108,6 @@ export default function Wrapper({ competitionId, wcifEvents, wcifSchedule, - countryZones, referenceTime, calendarLocale, }) { @@ -178,7 +123,6 @@ export default function Wrapper({ From 78b605fe7a7e61fed5fb6841d9abcf0d51e7533f Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:23:47 -0700 Subject: [PATCH 11/33] Delete EditVenues folder from EditSchedule folder --- .../EditSchedule/EditVenues/RoomPanel.js | 81 -------- .../EditVenues/VenueLocationMap.js | 128 ------------ .../EditSchedule/EditVenues/VenuePanel.js | 184 ------------------ .../EditSchedule/EditVenues/index.js | 51 ----- 4 files changed, 444 deletions(-) delete mode 100644 app/webpacker/components/EditSchedule/EditVenues/RoomPanel.js delete mode 100644 app/webpacker/components/EditSchedule/EditVenues/VenueLocationMap.js delete mode 100644 app/webpacker/components/EditSchedule/EditVenues/VenuePanel.js delete mode 100644 app/webpacker/components/EditSchedule/EditVenues/index.js diff --git a/app/webpacker/components/EditSchedule/EditVenues/RoomPanel.js b/app/webpacker/components/EditSchedule/EditVenues/RoomPanel.js deleted file mode 100644 index 4946f4a03d1..00000000000 --- a/app/webpacker/components/EditSchedule/EditVenues/RoomPanel.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { - Button, - Card, - Form, - Icon, - Popup, -} from 'semantic-ui-react'; -import { useDispatch } from '../../../lib/providers/StoreProvider'; -import { useConfirm } from '../../../lib/providers/ConfirmProvider'; -import { copyRoom, editRoom, removeRoom } from '../store/actions'; - -function RoomPanel({ - room, -}) { - const dispatch = useDispatch(); - - const confirm = useConfirm(); - - const handleChange = (evt, { name, value }) => { - dispatch(editRoom(room.id, name, value)); - }; - - const handleDeleteRoom = () => { - confirm({ - content: `Are you sure you want to delete the room ${room.name}? This will also delete all associated schedules. THIS ACTION CANNOT BE UNDONE!`, - }).then(() => dispatch(removeRoom(room.id))); - }; - - const handleCopyRoom = () => { - dispatch(copyRoom(room.id)); - }; - - return ( - - - - - - - )} - /> - - - - )} - /> - - -
- - - -
-
-
- ); -} - -export default RoomPanel; diff --git a/app/webpacker/components/EditSchedule/EditVenues/VenueLocationMap.js b/app/webpacker/components/EditSchedule/EditVenues/VenueLocationMap.js deleted file mode 100644 index 4bfdaffd767..00000000000 --- a/app/webpacker/components/EditSchedule/EditVenues/VenueLocationMap.js +++ /dev/null @@ -1,128 +0,0 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { - MapContainer, - Marker, - Popup, - TileLayer, - useMap, -} from 'react-leaflet'; -import { toDegrees, toMicrodegrees } from '../../../lib/utils/edit-schedule'; -import { userTileProvider } from '../../../lib/leaflet-wca/providers'; -import { useDispatch } from '../../../lib/providers/StoreProvider'; -import { editVenue } from '../store/actions'; -import ResizeMapIFrame from '../../../lib/utils/leaflet-iframe'; - -function GeoSearchControl({ - onGeoSearchResult, -}) { - const map = useMap(); - - useEffect(() => { - const searchControl = window.wca.createSearchInput(map); - - map.on('geosearch/showlocation', onGeoSearchResult); - map.zoomControl.setPosition('bottomright'); - - return () => { - map.removeControl(searchControl); - }; - }, [map, onGeoSearchResult]); - - return null; -} - -export function DraggableMarker({ - position, - setPosition, - disabled = false, - markerRef = null, - children, -}) { - const map = useMap(); - - const updatePosition = useCallback((e) => setPosition(e, e.target.getLatLng()), [setPosition]); - - useEffect(() => { - map.panTo(position); - }, [map, position]); - - return ( - - {children} - - ); -} - -function VenueLocationMap({ - venue, -}) { - const dispatch = useDispatch(); - const markerRef = useRef(); - - const [searchResultPopup, setSearchResultPopup] = useState(); - - const markerPopup = useMemo(() => { - if (searchResultPopup) { - return {searchResultPopup}; - } - - return null; - }, [searchResultPopup]); - - const venuePosition = useMemo(() => ({ - lat: toDegrees(venue.latitudeMicrodegrees), - lng: toDegrees(venue.longitudeMicrodegrees), - }), [venue.latitudeMicrodegrees, venue.longitudeMicrodegrees]); - - const setVenuePosition = useCallback((evt, { lat, lng }) => { - dispatch(editVenue(venue.id, 'latitudeMicrodegrees', toMicrodegrees(lat))); - dispatch(editVenue(venue.id, 'longitudeMicrodegrees', toMicrodegrees(lng))); - }, [dispatch, venue.id]); - - const provider = userTileProvider; - - const onGeoSearchResult = useCallback((evt) => { - setVenuePosition(evt, { - lat: evt.location.y, - lng: evt.location.x, - }); - - setSearchResultPopup(evt.location.label); - }, [setVenuePosition, setSearchResultPopup]); - - return ( - - - - - - {markerPopup} - - - ); -} - -export default VenueLocationMap; diff --git a/app/webpacker/components/EditSchedule/EditVenues/VenuePanel.js b/app/webpacker/components/EditSchedule/EditVenues/VenuePanel.js deleted file mode 100644 index cc153c561e8..00000000000 --- a/app/webpacker/components/EditSchedule/EditVenues/VenuePanel.js +++ /dev/null @@ -1,184 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { - Button, - Card, - Container, - DropdownHeader, - Form, - Icon, - Image, -} from 'semantic-ui-react'; -import _ from 'lodash'; - -import VenueLocationMap from './VenueLocationMap'; -import { countries, backendTimezones } from '../../../lib/wca-data.js.erb'; -import RoomPanel from './RoomPanel'; -import { useDispatch } from '../../../lib/providers/StoreProvider'; -import { useConfirm } from '../../../lib/providers/ConfirmProvider'; -import { - addRoom, - editVenue, - removeVenue, -} from '../store/actions'; -import { toDegrees, toMicrodegrees } from '../../../lib/utils/edit-schedule'; -import { getTimeZoneDropdownLabel, sortByOffset } from '../../../lib/utils/timezone'; - -const countryOptions = countries.real.map((country) => ({ - key: country.iso2, - text: country.name, - value: country.iso2, - flag: country.iso2.toLowerCase(), -})); - -function VenuePanel({ - venue, - countryZones, - referenceTime, -}) { - const dispatch = useDispatch(); - const confirm = useConfirm(); - - const handleCoordinateChange = (evt, { name, value }) => { - dispatch(editVenue(venue.id, name, toMicrodegrees(value))); - }; - - const handleVenueChange = (evt, { name, value }) => { - dispatch(editVenue(venue.id, name, value)); - }; - - const handleDeleteVenue = () => { - confirm({ - content: `Are you sure you want to delete the venue ${venue.name}? This will also delete all associated rooms and all associated schedules. THIS ACTION CANNOT BE UNDONE!`, - }).then(() => dispatch(removeVenue(venue.id))); - }; - - const handleAddRoom = () => { - dispatch(addRoom(venue.id)); - }; - - const getVenueTzDropdownLabel = useCallback( - // The whole page is not localized yet, so we just hard-code US English here as well. - (tzId) => getTimeZoneDropdownLabel(tzId, referenceTime, 'en-US'), - [referenceTime], - ); - - const makeTimeZoneOption = useCallback((key) => ({ - key, - text: getVenueTzDropdownLabel(key), - value: key, - }), [getVenueTzDropdownLabel]); - - // Instead of giving *all* TZInfo, use uniq-fied rails "meaningful" subset - // We'll add the "country_zones" to that, because some of our competitions - // use TZs not included in this subset. - // We want to display the "country_zones" first, so that it's more convenient for the user. - // In the end the array should look like that: - // - country_zone_a, country_zone_b, [...], other_tz_a, other_tz_b, [...] - const timezoneOptions = useMemo(() => { - // Stuff that is recommended based on the country list - const competitionZoneIds = _.uniq(countryZones); - const sortedCompetitionZones = sortByOffset(competitionZoneIds, referenceTime); - - // Stuff that is listed in our `backendTimezones` list but not in the preferred country list - const otherZoneIds = _.difference(backendTimezones, competitionZoneIds); - const sortedOtherZones = sortByOffset(otherZoneIds, referenceTime); - - // Both merged together, with the countryZone entries listed first. - return [ - { - as: DropdownHeader, - key: 'local-zones-header', - text: 'Local time zones', - disabled: true, - }, - ...sortedCompetitionZones.map(makeTimeZoneOption), - { - as: DropdownHeader, - key: 'other-zones-header', - text: 'Other time zones', - disabled: true, - }, - ...sortedOtherZones.map(makeTimeZoneOption), - ]; - }, [countryZones, referenceTime, makeTimeZoneOption]); - - return ( - - { /* Needs the className 'image' so that SemUI fills the top of the card */ } - - - - - - - - -
- - - - - - - - -
-
- - - - Rooms - - - - {venue.rooms.map((room) => ( - - ))} - - - -
- ); -} - -export default VenuePanel; diff --git a/app/webpacker/components/EditSchedule/EditVenues/index.js b/app/webpacker/components/EditSchedule/EditVenues/index.js deleted file mode 100644 index 5fba7373db4..00000000000 --- a/app/webpacker/components/EditSchedule/EditVenues/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { - Button, - Card, - Container, - Icon, - Segment, -} from 'semantic-ui-react'; -import { useDispatch, useStore } from '../../../lib/providers/StoreProvider'; -import VenuePanel from './VenuePanel'; -import { addVenue } from '../store/actions'; - -function EditVenues({ - countryZones, - referenceTime, -}) { - const { wcifSchedule } = useStore(); - - const dispatch = useDispatch(); - - const handleAddVenue = () => { - dispatch(addVenue()); - }; - - return ( -
- - - Please add all your venues and rooms below: - - - - - {wcifSchedule.venues.map((venue) => ( - - ))} - - -
- ); -} - -export default EditVenues; From 604fb6e32b3814fa81f2a44c030501d2e3b5b247 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:26:54 -0700 Subject: [PATCH 12/33] Remove venue actions from schedule reducer --- .../components/EditSchedule/store/actions.js | 109 ------------- .../components/EditSchedule/store/reducer.js | 148 +----------------- 2 files changed, 2 insertions(+), 255 deletions(-) diff --git a/app/webpacker/components/EditSchedule/store/actions.js b/app/webpacker/components/EditSchedule/store/actions.js index 660f946ba89..dbf81e76d01 100644 --- a/app/webpacker/components/EditSchedule/store/actions.js +++ b/app/webpacker/components/EditSchedule/store/actions.js @@ -4,14 +4,6 @@ export const EditActivity = 'EDIT_ACTIVITY'; export const RemoveActivity = 'REMOVE_ACTIVITY'; export const MoveActivity = 'MOVE_ACTIVITY'; export const ScaleActivity = 'SCALE_ACTIVITY'; -export const EditVenue = 'EDIT_VENUE'; -export const EditRoom = 'EDIT_ROOM'; -export const RemoveVenue = 'REMOVE_VENUE'; -export const RemoveRoom = 'REMOVE_ROOM'; -export const AddVenue = 'ADD_VENUE'; -export const AddRoom = 'ADD_ROOM'; -export const CopyVenue = 'COPY_VENUE'; -export const CopyRoom = 'COPY_ROOM'; export const CopyRoomActivities = 'COPY_ROOM_ACTIVITIES'; /** @@ -103,107 +95,6 @@ export const scaleActivity = (activityId, isoDeltaStart, isoDeltaEnd, updateMatc }, }); -/** - * Action creator for changing a venue's properties. - * @param {int} venueId - * @param {string} propertyKey - * @param {string} newProperty - * @returns {Action} - */ -export const editVenue = (venueId, propertyKey, newProperty) => ({ - type: EditVenue, - payload: { - venueId, - propertyKey, - newProperty, - }, -}); - -/** - * Action creator for changing a room's properties. - * @param {int} roomId - * @param {string} propertyKey - * @param {string} newProperty - * @returns {Action} - */ -export const editRoom = (roomId, propertyKey, newProperty) => ({ - type: EditRoom, - payload: { - roomId, - propertyKey, - newProperty, - }, -}); - -/** - * Action creator for removing a venue. - * @param {int} venueId - * @returns {Action} - */ -export const removeVenue = (venueId) => ({ - type: RemoveVenue, - payload: { - venueId, - }, -}); - -/** - * Action creator for removing a room. - * @param {int} roomId - * @returns {Action} - */ -export const removeRoom = (roomId) => ({ - type: RemoveRoom, - payload: { - roomId, - }, -}); - -/** - * Action creator for adding a blank venue. - * @returns {Action} - */ -export const addVenue = () => ({ - type: AddVenue, - payload: {}, -}); - -/** - * Action creator for adding a blank room. - * @param {int} venueId - * @returns {Action} - */ -export const addRoom = (venueId) => ({ - type: AddRoom, - payload: { - venueId, - }, -}); - -/** - * Action creator for copying a venue. - * @param {int} venueId - * @returns {Action} - */ -export const copyVenue = (venueId) => ({ - type: CopyVenue, - payload: { - venueId, - }, -}); - -/** - * Action creator for copying a room. - * @param {int} roomId - * @returns {Action} - */ -export const copyRoom = (roomId) => ({ - type: CopyRoom, - payload: { - roomId, - }, -}); - /** * Action creator for copying a room's activities to another room. * @param {int} sourceRoomId diff --git a/app/webpacker/components/EditSchedule/store/reducer.js b/app/webpacker/components/EditSchedule/store/reducer.js index f9ba3dcb17f..51c1faa6d03 100644 --- a/app/webpacker/components/EditSchedule/store/reducer.js +++ b/app/webpacker/components/EditSchedule/store/reducer.js @@ -1,25 +1,17 @@ import { AddActivity, - AddRoom, - AddVenue, ChangesSaved, - CopyRoom, CopyRoomActivities, - CopyVenue, EditActivity, - EditRoom, - EditVenue, MoveActivity, RemoveActivity, - RemoveRoom, - RemoveVenue, ScaleActivity, } from './actions'; import { - copyActivity, copyRoom, copyVenue, nextActivityId, nextRoomId, nextVenueId, + copyActivity, nextActivityId, } from '../../../lib/utils/edit-schedule'; import { - changeActivityTimezone, moveActivityByDuration, scaleActivitiesByDuration, + moveActivityByDuration, scaleActivitiesByDuration, } from '../utils'; import { activityWcifFromId, @@ -27,7 +19,6 @@ import { roomWcifFromId, venueWcifFromRoomId, } from '../../../lib/utils/wcif'; -import { defaultRoomColor } from '../../../lib/wca-data.js.erb'; const reducers = { [ChangesSaved]: (state) => ({ @@ -149,141 +140,6 @@ const reducers = { }; }, - [EditVenue]: (state, { payload }) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.map((venue) => (venue.id === payload.venueId ? { - ...venue, - [payload.propertyKey]: payload.newProperty, - rooms: payload.propertyKey === 'timezone' - ? venue.rooms.map((room) => ({ - ...room, - activities: room.activities.map((activity) => ( - changeActivityTimezone( - activity, - venue.timezone, - payload.newProperty, - ) - )), - })) - : venue.rooms, - } : venue)), - }, - }), - - [EditRoom]: (state, { payload }) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.map((venue) => ({ - ...venue, - rooms: venue.rooms.map((room) => (room.id === payload.roomId ? { - ...room, - [payload.propertyKey]: payload.newProperty, - } : room)), - })), - }, - }), - - [RemoveVenue]: (state, { payload }) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.filter((venue) => venue.id !== payload.venueId), - }, - }), - - [RemoveRoom]: (state, { payload }) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.map((venue) => ({ - ...venue, - rooms: venue.rooms.filter((room) => room.id !== payload.roomId), - })), - }, - }), - - [AddVenue]: (state) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: [ - ...state.wcifSchedule.venues, - { - id: nextVenueId(state.wcifSchedule), - latitudeMicrodegrees: 0, - longitudeMicrodegrees: 0, - rooms: [], - extensions: [], - }, - ], - }, - }), - - [AddRoom]: (state, { payload }) => ({ - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.map((venue) => (venue.id === payload.venueId ? { - ...venue, - rooms: [ - ...venue.rooms, - { - id: nextRoomId(state.wcifSchedule), - color: defaultRoomColor, - activities: [], - extensions: [], - }, - ], - } : venue)), - }, - }), - - [CopyVenue]: (state, { payload }) => { - const venue = state.wcifSchedule.venues.find(({ id }) => id === payload.venueId); - if (!venue) return state; - - return { - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: [ - ...state.wcifSchedule.venues, - { - ...copyVenue(state.wcifSchedule, venue), - name: `Copy of ${venue.name}`, - }, - ], - }, - }; - }, - - [CopyRoom]: (state, { payload }) => { - const targetVenue = venueWcifFromRoomId(state.wcifSchedule, payload.roomId); - if (!targetVenue) return state; - const room = targetVenue.rooms.find(({ id }) => id === payload.roomId); - if (!room) return state; - - return { - ...state, - wcifSchedule: { - ...state.wcifSchedule, - venues: state.wcifSchedule.venues.map((venue) => (venue.id === targetVenue.id ? { - ...venue, - rooms: [ - ...venue.rooms, - { - ...copyRoom(state.wcifSchedule, room), - name: `Copy of ${room.name}`, - }, - ], - } : venue)), - }, - }; - }, - [CopyRoomActivities]: (state, { payload }) => { const { sourceRoomId, targetRoomId } = payload; const sourceRoomActivities = roomWcifFromId(state.wcifSchedule, sourceRoomId).activities; From f6a2bc9bd7193f6c5b0e8712c7ade3c8749976dc Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:28:40 -0700 Subject: [PATCH 13/33] Remove unused schedule utils --- app/webpacker/components/EditSchedule/utils.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/webpacker/components/EditSchedule/utils.js b/app/webpacker/components/EditSchedule/utils.js index 6495a75f474..99d9407df3f 100644 --- a/app/webpacker/components/EditSchedule/utils.js +++ b/app/webpacker/components/EditSchedule/utils.js @@ -1,6 +1,5 @@ import { addIsoDurations, - changeTimezoneKeepingLocalTime, millisecondsBetween, moveByIsoDuration, rescaleIsoDuration, @@ -91,16 +90,3 @@ export const scaleActivitiesByDuration = (activity, isoDeltaStart, isoDeltaEnd) }), }); }; - -export const changeActivityTimezone = (activity, oldTimezone, newTimezone) => ({ - ...activity, - startTime: changeTimezoneKeepingLocalTime(activity.startTime, oldTimezone, newTimezone), - endTime: changeTimezoneKeepingLocalTime(activity.endTime, oldTimezone, newTimezone), - childActivities: activity.childActivities.map((childActivity) => ( - changeActivityTimezone( - childActivity, - oldTimezone, - newTimezone, - ) - )), -}); From 35dc828cf89b5d4d05e2a9a39b9d9f6cf8ff88bb Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:30:17 -0700 Subject: [PATCH 14/33] Rename folders --- .../{EditActivities => ManageActivities}/ActionsHeader.js | 0 .../{EditActivities => ManageActivities}/ActivityPicker.js | 0 .../{EditActivities => ManageActivities}/EditActivityModal.js | 0 .../{EditActivities => ManageActivities}/index.js | 0 app/webpacker/components/EditSchedule/index.js | 4 ++-- .../EditVenues/{EditVenues => ManageVenues}/RoomPanel.js | 0 .../{EditVenues => ManageVenues}/VenueLocationMap.js | 0 .../EditVenues/{EditVenues => ManageVenues}/VenuePanel.js | 0 .../EditVenues/{EditVenues => ManageVenues}/index.js | 0 app/webpacker/components/EditVenues/index.js | 4 ++-- 10 files changed, 4 insertions(+), 4 deletions(-) rename app/webpacker/components/EditSchedule/{EditActivities => ManageActivities}/ActionsHeader.js (100%) rename app/webpacker/components/EditSchedule/{EditActivities => ManageActivities}/ActivityPicker.js (100%) rename app/webpacker/components/EditSchedule/{EditActivities => ManageActivities}/EditActivityModal.js (100%) rename app/webpacker/components/EditSchedule/{EditActivities => ManageActivities}/index.js (100%) rename app/webpacker/components/EditVenues/{EditVenues => ManageVenues}/RoomPanel.js (100%) rename app/webpacker/components/EditVenues/{EditVenues => ManageVenues}/VenueLocationMap.js (100%) rename app/webpacker/components/EditVenues/{EditVenues => ManageVenues}/VenuePanel.js (100%) rename app/webpacker/components/EditVenues/{EditVenues => ManageVenues}/index.js (100%) diff --git a/app/webpacker/components/EditSchedule/EditActivities/ActionsHeader.js b/app/webpacker/components/EditSchedule/ManageActivities/ActionsHeader.js similarity index 100% rename from app/webpacker/components/EditSchedule/EditActivities/ActionsHeader.js rename to app/webpacker/components/EditSchedule/ManageActivities/ActionsHeader.js diff --git a/app/webpacker/components/EditSchedule/EditActivities/ActivityPicker.js b/app/webpacker/components/EditSchedule/ManageActivities/ActivityPicker.js similarity index 100% rename from app/webpacker/components/EditSchedule/EditActivities/ActivityPicker.js rename to app/webpacker/components/EditSchedule/ManageActivities/ActivityPicker.js diff --git a/app/webpacker/components/EditSchedule/EditActivities/EditActivityModal.js b/app/webpacker/components/EditSchedule/ManageActivities/EditActivityModal.js similarity index 100% rename from app/webpacker/components/EditSchedule/EditActivities/EditActivityModal.js rename to app/webpacker/components/EditSchedule/ManageActivities/EditActivityModal.js diff --git a/app/webpacker/components/EditSchedule/EditActivities/index.js b/app/webpacker/components/EditSchedule/ManageActivities/index.js similarity index 100% rename from app/webpacker/components/EditSchedule/EditActivities/index.js rename to app/webpacker/components/EditSchedule/ManageActivities/index.js diff --git a/app/webpacker/components/EditSchedule/index.js b/app/webpacker/components/EditSchedule/index.js index 00a426305dd..f2f6268811f 100644 --- a/app/webpacker/components/EditSchedule/index.js +++ b/app/webpacker/components/EditSchedule/index.js @@ -17,7 +17,7 @@ import { changesSaved } from './store/actions'; import wcifScheduleReducer from './store/reducer'; import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider'; import ConfirmProvider from '../../lib/providers/ConfirmProvider'; -import EditActivities from './EditActivities'; +import ManageActivities from './ManageActivities'; function EditSchedule({ wcifEvents, @@ -93,7 +93,7 @@ function EditSchedule({ {renderIntroductionMessage()}
{unsavedChanges && renderUnsavedChangesAlert()} - {unsavedChanges && renderUnsavedChangesAlert()} - From 21cf6172d698c779bde5f905751c3534050eb9f9 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:35:18 -0700 Subject: [PATCH 15/33] Remove id from EditVenues react_component --- app/views/competitions/edit_venues.html.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/competitions/edit_venues.html.erb b/app/views/competitions/edit_venues.html.erb index 4ce9d1dcef2..943a2ed027f 100644 --- a/app/views/competitions/edit_venues.html.erb +++ b/app/views/competitions/edit_venues.html.erb @@ -20,8 +20,6 @@ wcifSchedule: @competition.schedule_wcif, countryZones: @competition.country_zones, referenceTime: @competition.start_date.to_fs, - }, { - id: 'edit-schedule-area' }) %> <% end %> <% end %> From fda75f27c75826cd8c03add9eebe837a66f89bbe Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:37:29 -0700 Subject: [PATCH 16/33] Remove id from EditSchedule react_component --- app/views/competitions/edit_schedule.html.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/competitions/edit_schedule.html.erb b/app/views/competitions/edit_schedule.html.erb index c7eae9f91a4..7f056b0fe81 100644 --- a/app/views/competitions/edit_schedule.html.erb +++ b/app/views/competitions/edit_schedule.html.erb @@ -21,8 +21,6 @@ wcifSchedule: @competition.schedule_wcif, referenceTime: @competition.start_date.to_fs, calendarLocale: I18n.locale, - }, { - id: 'edit-schedule-area' }) %> <% end %> <% end %> From 4ca7687fdad9a8254693114e2b8e87169212e047 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:39:42 -0700 Subject: [PATCH 17/33] Remove edit_schedule.scss not actually doing anything, as far as I can tell --- app/assets/stylesheets/application.css.scss | 1 - app/assets/stylesheets/edit_schedule.scss | 194 -------------------- 2 files changed, 195 deletions(-) delete mode 100644 app/assets/stylesheets/edit_schedule.scss diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 218aedbf5f0..2b4ab0044ba 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -50,7 +50,6 @@ @import "server_status"; @import "static_pages"; @import "edit_events"; -@import "edit_schedule"; @import "media"; @import "incidents"; @import "translations"; diff --git a/app/assets/stylesheets/edit_schedule.scss b/app/assets/stylesheets/edit_schedule.scss deleted file mode 100644 index 9673da238b2..00000000000 --- a/app/assets/stylesheets/edit_schedule.scss +++ /dev/null @@ -1,194 +0,0 @@ -#edit-schedule-area { - .panel-primary { - .collapse-indicator { - color: #fff; - } - } - - #venues-edit-panel { - .leaflet-container { - height: 300px; - } - .venue-form-label { - @extend label; - } - .panel-venue { - margin-bottom: 15px; - .new-venue-link { - width: 100%; - } - .venue-title { - padding-top: 7px; - } - .panel-body { - .row { - margin-bottom: 10px; - .room-row { - margin-bottom: 20px; - .room-color-cell { - padding-top: 5px; - } - } - } - } - } - } - - #schedules-edit-panel { - #activity-picker-panel { - &.affix { - top: 10px; - } - &.affix-bottom { - position: absolute; - bottom: auto; - } - .selected-activity { - border: 3px solid $selected-activity-border-color; - margin-top: 1px; - margin-bottom: 1px; - } - .panel-heading { - @extend .text-center; - } - .panel-body { - padding: 5px; - overflow-y: auto; - } - .event-picker-line { - .row { - min-height: 32px; - .activity-icon { - padding: 0; - .cubing-icon { - font-size: 22px; - &::before { - margin-top: 2px; - } - } - } - .activity-in-picker { - padding: 0; - } - } - } - } - .room-selector { - label { - padding-top: 7px; - } - } - #schedule-editor { - #schedule-menu { - width: 150px; - position: fixed; - top: 100px; - left: 100px; - display: block; - &.hide-element { - display: none; - } - &.delete-only { - .edit-option { - display: none; - } - } - a { - padding: 3px 20px; - i { - margin-right: 10px; - } - } - } - - #schedule-calendar { - &:target { - // Override the flashy yellow we use for targets elsewhere - background-color: transparent; - } - - // Events get added to 'fc-helper-container' when they are dragged/resized, - // so we need to consider any event there as selected. - .fc-helper-container > .fc-event, - .fc-event.selected-fc-event { - border: 2px solid $selected-activity-border-color; - } - - } - #drop-event-area { - z-index: 0; - - display: flex; - align-items: center; - justify-content: space-around; - padding: 10px; - - margin-bottom: 15px; - border-radius: 0.25em; - border: 2px dashed #a94442; - &.event-on-top { - border-color: #f2dede; - color: #f2dede; - background-color: #a94442; - } - } - } - } -} - -#tooltip-enable-keyboard { - .tooltip-inner { - max-width: none; - } -} - -#calendar-settings-popover { - .setting-label { - padding-top: 7px; - padding-right: 5px; - padding-left: 0; - font-size: 12px; - text-align: right; - } - select { - @extend .input-sm; - margin-bottom: 5px; - } -} - -// This needs to be here, to overcome an issue with draggable activity + overflow container -.schedule-activity { - background-color: $activity-bg-color; - color: $activity-text-color; - font-weight: bold; - border-radius: 0.25em; - margin: 3px 2px; - padding-left: 2px; - padding-right: 2px; - text-align: center; - cursor: pointer; - z-index: 3; - &.activity-used { - opacity: 0.5; - } -} - -// The popover gets appended to , so we can't nest this style -#calendar-help-popover { - width: 400px; - dl { - dt { - margin-bottom: 10px; - text-align: right; - padding-right: 0; - padding-left: 5px; - &::after { - content: ":"; - } - } - dd { - margin-bottom: 10px; - padding-left: 5px; - } - } -} From aff6d0df94fb68d43e7fcc7ee38b338f1ac14540 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 16:50:06 -0700 Subject: [PATCH 18/33] Rename new component to EditVenues --- app/webpacker/components/EditVenues/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js index 6d19e0d4aaf..0a00d83406c 100644 --- a/app/webpacker/components/EditVenues/index.js +++ b/app/webpacker/components/EditVenues/index.js @@ -19,7 +19,7 @@ import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider' import ConfirmProvider from '../../lib/providers/ConfirmProvider'; import ManageVenues from './ManageVenues'; -function EditSchedule({ +function EditVenues({ countryZones, referenceTime, }) { @@ -131,7 +131,7 @@ export default function Wrapper({ }} > - From 66333fd48d1132f6cf6c6018aafeccc115aad6ee Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Fri, 4 Oct 2024 17:18:45 -0700 Subject: [PATCH 19/33] eslint --- app/webpacker/components/EditSchedule/index.js | 13 +++++++------ .../components/EditVenues/store/reducer.js | 2 +- app/webpacker/components/EditVenues/utils.js | 4 +++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/webpacker/components/EditSchedule/index.js b/app/webpacker/components/EditSchedule/index.js index f2f6268811f..62417d74984 100644 --- a/app/webpacker/components/EditSchedule/index.js +++ b/app/webpacker/components/EditSchedule/index.js @@ -83,7 +83,8 @@ function EditSchedule({ const renderIntroductionMessage = () => (

- To create a schedule, first visit the "Manage venues" panel to add venues and rooms/stages. + To create a schedule, first visit the "Manage venues" panel to add venues and + rooms/stages.

); @@ -93,11 +94,11 @@ function EditSchedule({ {renderIntroductionMessage()}
{unsavedChanges && renderUnsavedChangesAlert()} - + {unsavedChanges && renderUnsavedChangesAlert()}
diff --git a/app/webpacker/components/EditVenues/store/reducer.js b/app/webpacker/components/EditVenues/store/reducer.js index b1c81d587bd..6d4800a77eb 100644 --- a/app/webpacker/components/EditVenues/store/reducer.js +++ b/app/webpacker/components/EditVenues/store/reducer.js @@ -12,7 +12,7 @@ import { import { copyRoom, copyVenue, nextRoomId, nextVenueId, } from '../../../lib/utils/edit-schedule'; -import { changeActivityTimezone } from '../utils'; +import changeActivityTimezone from '../utils'; import { venueWcifFromRoomId } from '../../../lib/utils/wcif'; import { defaultRoomColor } from '../../../lib/wca-data.js.erb'; diff --git a/app/webpacker/components/EditVenues/utils.js b/app/webpacker/components/EditVenues/utils.js index 6e4ec6d01a6..638b64d9ab0 100644 --- a/app/webpacker/components/EditVenues/utils.js +++ b/app/webpacker/components/EditVenues/utils.js @@ -1,6 +1,6 @@ import { changeTimezoneKeepingLocalTime } from '../../lib/utils/edit-schedule'; -export const changeActivityTimezone = (activity, oldTimezone, newTimezone) => ({ +const changeActivityTimezone = (activity, oldTimezone, newTimezone) => ({ ...activity, startTime: changeTimezoneKeepingLocalTime(activity.startTime, oldTimezone, newTimezone), endTime: changeTimezoneKeepingLocalTime(activity.endTime, oldTimezone, newTimezone), @@ -12,3 +12,5 @@ export const changeActivityTimezone = (activity, oldTimezone, newTimezone) => ({ ) )), }); + +export default changeActivityTimezone; From c6475428969cd791b8dd7392ffc2f6f57e3869e6 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:46:57 -0700 Subject: [PATCH 20/33] Rename RSpec.feature --- spec/features/competition_manage_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/competition_manage_schedule_spec.rb b/spec/features/competition_manage_schedule_spec.rb index 0db74ec84c1..6404a3a36e9 100644 --- a/spec/features/competition_manage_schedule_spec.rb +++ b/spec/features/competition_manage_schedule_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.feature "Competition events management" do +RSpec.feature "Competition schedule management" do before :each do # Enable CSRF protection just for these tests. # See https://blog.tomoyukikashiro.me/post/test-csrf-in-feature-test-using-capybara/ From ba28af6af65496207882b6dcfa3866ad639ef72f Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:48:29 -0700 Subject: [PATCH 21/33] Duplicate manage schedule spec for manage venues --- .../competition_manage_venues_spec.rb | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 spec/features/competition_manage_venues_spec.rb diff --git a/spec/features/competition_manage_venues_spec.rb b/spec/features/competition_manage_venues_spec.rb new file mode 100644 index 00000000000..312744fe327 --- /dev/null +++ b/spec/features/competition_manage_venues_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.feature "Competition venues management" do + before :each do + # Enable CSRF protection just for these tests. + # See https://blog.tomoyukikashiro.me/post/test-csrf-in-feature-test-using-capybara/ + allow_any_instance_of(ActionController::Base).to receive(:protect_against_forgery?).and_return(true) + end + + context "unconfirmed competition without schedule" do + let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, event_ids: ["333", "444"], with_rounds: true) } + background do + sign_in competition.delegates.first + visit "/competitions/#{competition.id}/schedule/edit" + end + + scenario "can add a venue and a room", js: true do + find("div", class: 'title', text: 'Edit venues information').click + + within(:css, "#venues-edit-panel-body") do + click_button "Add a venue" + fill_in("venue-name", with: "Venue") + click_button "Add room" + fill_in("room-name", with: "Youpitralala") + within(:css, "div[name='timezone'][role='listbox']>div.menu", visible: :all) do + # Using a timezone that does not follow Daylight Savings, so that we get consistent results all year round + find("div", class: "item", text: "Asia/Tokyo (Japan Standard Time, UTC+9)", visible: :all).trigger(:click) + end + within(:css, "div[name='countryIso2'][role='combobox']>div.menu[role='listbox']", visible: :all) do + find("div", class: "item", text: "United States", visible: :all).trigger(:click) + end + end + + save_schedule_react + + expect(competition.competition_venues.map(&:name)).to match_array %w(Venue) + expect(competition.competition_venues.flat_map(&:venue_rooms).map(&:name)).to match_array %w(Youpitralala) + end + end + + context "unconfirmed competition with schedule" do + let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, :with_valid_schedule, event_ids: ["333", "444"]) } + background do + sign_in competition.delegates.first + visit "/competitions/#{competition.id}/schedule/edit" + end + + scenario "room calendar is rendered", js: true do + find("div", class: 'title', text: 'Edit schedules').click + + within(:css, "#schedules-edit-panel-body") do + # click_link doesn't work because Capybara expects links to always have an href + find("a", class: 'item', text: "Room 1 for venue 1").click + # 2 is the number of non-nested activities created by the factory + # Nested activity are not supported (yet) in the schedule manager + expect(all('.fc-event').size).to eq(2) + end + end + end +end + +def save_schedule_react + first(:button, "save your changes!", visible: true).click + # Wait for ajax to complete. + expect(page).to have_no_content("You have unsaved changes") +end From 2850f2420e27cec0ac280f0c86aefbd92abf103e Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:50:07 -0700 Subject: [PATCH 22/33] Remove venue tests from schedule spec --- .../competition_manage_schedule_spec.rb | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/spec/features/competition_manage_schedule_spec.rb b/spec/features/competition_manage_schedule_spec.rb index 6404a3a36e9..da8c8a0cfaf 100644 --- a/spec/features/competition_manage_schedule_spec.rb +++ b/spec/features/competition_manage_schedule_spec.rb @@ -9,37 +9,6 @@ allow_any_instance_of(ActionController::Base).to receive(:protect_against_forgery?).and_return(true) end - context "unconfirmed competition without schedule" do - let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, event_ids: ["333", "444"], with_rounds: true) } - background do - sign_in competition.delegates.first - visit "/competitions/#{competition.id}/schedule/edit" - end - - scenario "can add a venue and a room", js: true do - find("div", class: 'title', text: 'Edit venues information').click - - within(:css, "#venues-edit-panel-body") do - click_button "Add a venue" - fill_in("venue-name", with: "Venue") - click_button "Add room" - fill_in("room-name", with: "Youpitralala") - within(:css, "div[name='timezone'][role='listbox']>div.menu", visible: :all) do - # Using a timezone that does not follow Daylight Savings, so that we get consistent results all year round - find("div", class: "item", text: "Asia/Tokyo (Japan Standard Time, UTC+9)", visible: :all).trigger(:click) - end - within(:css, "div[name='countryIso2'][role='combobox']>div.menu[role='listbox']", visible: :all) do - find("div", class: "item", text: "United States", visible: :all).trigger(:click) - end - end - - save_schedule_react - - expect(competition.competition_venues.map(&:name)).to match_array %w(Venue) - expect(competition.competition_venues.flat_map(&:venue_rooms).map(&:name)).to match_array %w(Youpitralala) - end - end - context "unconfirmed competition with schedule" do let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, :with_valid_schedule, event_ids: ["333", "444"]) } background do From bdb801cd434bf7743e47bb47a0f13fca7b5f98ac Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:51:06 -0700 Subject: [PATCH 23/33] Remove schedule tests from venues spec --- .../competition_manage_venues_spec.rb | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/spec/features/competition_manage_venues_spec.rb b/spec/features/competition_manage_venues_spec.rb index 312744fe327..537d52f0639 100644 --- a/spec/features/competition_manage_venues_spec.rb +++ b/spec/features/competition_manage_venues_spec.rb @@ -33,35 +33,16 @@ end end - save_schedule_react + save_venues_react expect(competition.competition_venues.map(&:name)).to match_array %w(Venue) expect(competition.competition_venues.flat_map(&:venue_rooms).map(&:name)).to match_array %w(Youpitralala) end end - context "unconfirmed competition with schedule" do - let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, :with_valid_schedule, event_ids: ["333", "444"]) } - background do - sign_in competition.delegates.first - visit "/competitions/#{competition.id}/schedule/edit" - end - - scenario "room calendar is rendered", js: true do - find("div", class: 'title', text: 'Edit schedules').click - - within(:css, "#schedules-edit-panel-body") do - # click_link doesn't work because Capybara expects links to always have an href - find("a", class: 'item', text: "Room 1 for venue 1").click - # 2 is the number of non-nested activities created by the factory - # Nested activity are not supported (yet) in the schedule manager - expect(all('.fc-event').size).to eq(2) - end - end - end end -def save_schedule_react +def save_venues_react first(:button, "save your changes!", visible: true).click # Wait for ajax to complete. expect(page).to have_no_content("You have unsaved changes") From dbaac69d371a54de44b3516a4c59454837531817 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:52:43 -0700 Subject: [PATCH 24/33] Remove clicking accord from tests since it doesn't exist anymore --- spec/features/competition_manage_schedule_spec.rb | 2 -- spec/features/competition_manage_venues_spec.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/spec/features/competition_manage_schedule_spec.rb b/spec/features/competition_manage_schedule_spec.rb index da8c8a0cfaf..a011b54bcbb 100644 --- a/spec/features/competition_manage_schedule_spec.rb +++ b/spec/features/competition_manage_schedule_spec.rb @@ -17,8 +17,6 @@ end scenario "room calendar is rendered", js: true do - find("div", class: 'title', text: 'Edit schedules').click - within(:css, "#schedules-edit-panel-body") do # click_link doesn't work because Capybara expects links to always have an href find("a", class: 'item', text: "Room 1 for venue 1").click diff --git a/spec/features/competition_manage_venues_spec.rb b/spec/features/competition_manage_venues_spec.rb index 537d52f0639..db222c078dd 100644 --- a/spec/features/competition_manage_venues_spec.rb +++ b/spec/features/competition_manage_venues_spec.rb @@ -17,8 +17,6 @@ end scenario "can add a venue and a room", js: true do - find("div", class: 'title', text: 'Edit venues information').click - within(:css, "#venues-edit-panel-body") do click_button "Add a venue" fill_in("venue-name", with: "Venue") From ce584894c705f57f175ef1773e1d91c2a6a0b1b2 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sat, 5 Oct 2024 11:55:02 -0700 Subject: [PATCH 25/33] Remove extra empty line --- spec/features/competition_manage_venues_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/competition_manage_venues_spec.rb b/spec/features/competition_manage_venues_spec.rb index db222c078dd..8576008cb0a 100644 --- a/spec/features/competition_manage_venues_spec.rb +++ b/spec/features/competition_manage_venues_spec.rb @@ -37,7 +37,6 @@ expect(competition.competition_venues.flat_map(&:venue_rooms).map(&:name)).to match_array %w(Youpitralala) end end - end def save_venues_react From 8f0a4f64d7b9388e6046cc6e3e382fb7fba659de Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sun, 6 Oct 2024 11:45:03 -0700 Subject: [PATCH 26/33] Rename components to match folder name --- .../components/EditSchedule/ManageActivities/index.js | 4 ++-- app/webpacker/components/EditVenues/ManageVenues/index.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/EditSchedule/ManageActivities/index.js b/app/webpacker/components/EditSchedule/ManageActivities/index.js index e2edc39abb3..a6985119277 100644 --- a/app/webpacker/components/EditSchedule/ManageActivities/index.js +++ b/app/webpacker/components/EditSchedule/ManageActivities/index.js @@ -54,7 +54,7 @@ import ActionsHeader from './ActionsHeader'; import { getTimeZoneDropdownLabel } from '../../../lib/utils/timezone'; import { earliestTimeOfDayWithBuffer } from '../../../lib/utils/activities'; -function EditActivities({ +function ManageActivities({ wcifEvents, referenceTime, calendarLocale, @@ -504,4 +504,4 @@ function EditActivities({ ); } -export default EditActivities; +export default ManageActivities; diff --git a/app/webpacker/components/EditVenues/ManageVenues/index.js b/app/webpacker/components/EditVenues/ManageVenues/index.js index 2bbe5c40396..6451455dac0 100644 --- a/app/webpacker/components/EditVenues/ManageVenues/index.js +++ b/app/webpacker/components/EditVenues/ManageVenues/index.js @@ -10,7 +10,7 @@ import { useDispatch, useStore } from '../../../lib/providers/StoreProvider'; import VenuePanel from './VenuePanel'; import { addVenue } from '../store/actions'; -function EditVenues({ +function ManageVenues({ countryZones, referenceTime, }) { @@ -48,4 +48,4 @@ function EditVenues({ ); } -export default EditVenues; +export default ManageVenues; From a2a4934a8f210a9ddbf0865bc16f0cc89bc8390b Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Sun, 6 Oct 2024 11:45:32 -0700 Subject: [PATCH 27/33] Fix url in manage venues spec --- spec/features/competition_manage_venues_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/competition_manage_venues_spec.rb b/spec/features/competition_manage_venues_spec.rb index 8576008cb0a..1eff31bd8aa 100644 --- a/spec/features/competition_manage_venues_spec.rb +++ b/spec/features/competition_manage_venues_spec.rb @@ -13,7 +13,7 @@ let!(:competition) { FactoryBot.create(:competition, :with_delegate, :registration_open, event_ids: ["333", "444"], with_rounds: true) } background do sign_in competition.delegates.first - visit "/competitions/#{competition.id}/schedule/edit" + visit "/competitions/#{competition.id}/venues/edit" end scenario "can add a venue and a room", js: true do From d715d9314dc845eb1b74b0ff2c66a63cc8b43dc5 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 13 Jan 2025 20:42:51 +0900 Subject: [PATCH 28/33] Add backend param to prevent schedule updates in WCIF patch --- app/controllers/api/v0/competitions_controller.rb | 6 ++++-- app/models/competition.rb | 8 ++++---- app/models/competition_venue.rb | 4 ++-- app/models/venue_room.rb | 12 +++++++----- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/v0/competitions_controller.rb b/app/controllers/api/v0/competitions_controller.rb index 9bbba881f6e..ec586018ce9 100644 --- a/app/controllers/api/v0/competitions_controller.rb +++ b/app/controllers/api/v0/competitions_controller.rb @@ -166,9 +166,11 @@ def show_wcif_public def update_wcif competition = competition_from_params require_can_manage!(competition) - wcif = params.permit!.to_h + skip_schedule = params[:'skip-schedule'] + skip_schedule = ActiveRecord::Type::Boolean.new.cast(skip_schedule) + wcif = params.except("skip-schedule").permit!.to_h wcif = wcif["_json"] || wcif - competition.set_wcif!(wcif, require_user!) + competition.set_wcif!(wcif, require_user!, skip_schedule: skip_schedule) render json: { status: "Successfully saved WCIF", } diff --git a/app/models/competition.rb b/app/models/competition.rb index 67a4989a041..81cee3c1711 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1963,13 +1963,13 @@ def schedule_wcif } end - def set_wcif!(wcif, current_user) + def set_wcif!(wcif, current_user, skip_schedule: false) JSON::Validator.validate!(Competition.wcif_json_schema, wcif) ActiveRecord::Base.transaction do set_wcif_series!(wcif["series"], current_user) if wcif["series"] set_wcif_events!(wcif["events"], current_user) if wcif["events"] - set_wcif_schedule!(wcif["schedule"], current_user) if wcif["schedule"] + set_wcif_schedule!(wcif["schedule"], current_user, skip_schedule: skip_schedule) if wcif["schedule"] update_persons_wcif!(wcif["persons"], current_user) if wcif["persons"] WcifExtension.update_wcif_extensions!(self, wcif["extensions"]) if wcif["extensions"] set_wcif_competitor_limit!(wcif["competitorLimit"], current_user) if wcif["competitorLimit"] @@ -2134,7 +2134,7 @@ def update_persons_wcif!(wcif_persons, current_user) Assignment.upsert_all(new_assignments) if new_assignments.any? end - def set_wcif_schedule!(wcif_schedule, current_user) + def set_wcif_schedule!(wcif_schedule, current_user, skip_schedule: false) if wcif_schedule["startDate"] != start_date.strftime("%F") raise WcaExceptions::BadApiParameter.new("Wrong start date for competition") elsif wcif_schedule["numberOfDays"] != number_of_days @@ -2153,7 +2153,7 @@ def set_wcif_schedule!(wcif_schedule, current_user) # using this find instead of ActiveRecord's find_or_create_by avoid several queries # (despite having the association included :() venue = competition_venues.find { |v| v.wcif_id == venue_wcif["id"] } || competition_venues.build - venue.load_wcif!(venue_wcif) + venue.load_wcif!(venue_wcif, skip_schedule: skip_schedule) end self.competition_venues = new_venues diff --git a/app/models/competition_venue.rb b/app/models/competition_venue.rb index 21aec40fd93..7b8dbc784f6 100644 --- a/app/models/competition_venue.rb +++ b/app/models/competition_venue.rb @@ -22,11 +22,11 @@ def country Country.find_by_iso2(self.country_iso2) end - def load_wcif!(wcif) + def load_wcif!(wcif, skip_schedule: false) update!(CompetitionVenue.wcif_to_attributes(wcif)) new_rooms = wcif["rooms"].map do |room_wcif| room = venue_rooms.find { |r| r.wcif_id == room_wcif["id"] } || venue_rooms.build - room.load_wcif!(room_wcif) + room.load_wcif!(room_wcif, skip_schedule: skip_schedule) end self.venue_rooms = new_rooms WcifExtension.update_wcif_extensions!(self, wcif["extensions"]) if wcif["extensions"] diff --git a/app/models/venue_room.rb b/app/models/venue_room.rb index d6f5152d693..8c337cfb15a 100644 --- a/app/models/venue_room.rb +++ b/app/models/venue_room.rb @@ -53,13 +53,15 @@ def self.wcif_json_schema } end - def load_wcif!(wcif) + def load_wcif!(wcif, skip_schedule: false) update!(VenueRoom.wcif_to_attributes(wcif)) - new_activities = wcif["activities"].map do |activity_wcif| - activity = schedule_activities.find { |a| a.wcif_id == activity_wcif["id"] } || schedule_activities.build - activity.load_wcif!(activity_wcif) + unless skip_schedule + new_activities = wcif["activities"].map do |activity_wcif| + activity = schedule_activities.find { |a| a.wcif_id == activity_wcif["id"] } || schedule_activities.build + activity.load_wcif!(activity_wcif) + end + self.schedule_activities = new_activities end - self.schedule_activities = new_activities WcifExtension.update_wcif_extensions!(self, wcif["extensions"]) if wcif["extensions"] self end From 66cf8c6e4693801f4d87ca2c63bb5092c6cfc13f Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 13 Jan 2025 20:48:21 +0900 Subject: [PATCH 29/33] Pull through skip_schedule param in frontend --- app/webpacker/components/EditVenues/index.js | 2 +- app/webpacker/lib/requests/routes.js.erb | 2 ++ app/webpacker/lib/utils/wcif.js | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js index 0a00d83406c..c49805e42a3 100644 --- a/app/webpacker/components/EditVenues/index.js +++ b/app/webpacker/components/EditVenues/index.js @@ -54,7 +54,7 @@ function EditVenues({ }; }, [onUnload]); - const { saveWcif, saving } = useSaveWcifAction(); + const { saveWcif, saving } = useSaveWcifAction(true); const save = useCallback(() => { saveWcif( diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 8430672929e..a22056b138f 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -298,3 +298,5 @@ export const updateRegistrationUrl = `<%= CGI.unescape(Rails.application.routes. export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_bulk_update_path) %>`; export const paymentTicketUrl = (competitionId, donationIso) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_payment_ticket_path(competition_id: "${competitionId}", donation_iso: "${donationIso}")) %>`; + +export const patchWcifUrl = (competitionId, skipSchedule = false) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_update_wcif_path(competition_id: "${competitionId}")) %>?skip-schedule=${skipSchedule}`; diff --git a/app/webpacker/lib/utils/wcif.js b/app/webpacker/lib/utils/wcif.js index 5e8a483f081..c66fba9e4c5 100644 --- a/app/webpacker/lib/utils/wcif.js +++ b/app/webpacker/lib/utils/wcif.js @@ -5,8 +5,9 @@ import I18n from '../i18n'; import { attemptResultToString, attemptResultToMbPoints } from './edit-events'; import useSaveAction from '../hooks/useSaveAction'; import { centisecondsToClockFormat } from '../wca-live/attempts'; +import { patchWcifUrl } from '../requests/routes.js.erb'; -export function useSaveWcifAction() { +export function useSaveWcifAction(skipSchedule = false) { const { save, saving } = useSaveAction(); const alertWcifError = (err) => { @@ -22,11 +23,11 @@ export function useSaveWcifAction() { options = {}, onError = alertWcifError, ) => { - const url = `/api/v0/competitions/${competitionId}/wcif`; + const url = patchWcifUrl(competitionId, skipSchedule); save(url, wcifData, onSuccess, options, onError); }, - [save], + [save, skipSchedule], ); return { From 2f8af2454b20475d1eecdf6bb3f5090e58e5ab0e Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 13 Jan 2025 20:57:30 +0900 Subject: [PATCH 30/33] Add another parameter for venue details --- app/controllers/api/v0/competitions_controller.rb | 6 ++++-- app/models/competition.rb | 8 ++++---- app/models/competition_venue.rb | 6 +++--- app/models/venue_room.rb | 4 ++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/v0/competitions_controller.rb b/app/controllers/api/v0/competitions_controller.rb index ec586018ce9..7d8d41545e3 100644 --- a/app/controllers/api/v0/competitions_controller.rb +++ b/app/controllers/api/v0/competitions_controller.rb @@ -168,9 +168,11 @@ def update_wcif require_can_manage!(competition) skip_schedule = params[:'skip-schedule'] skip_schedule = ActiveRecord::Type::Boolean.new.cast(skip_schedule) - wcif = params.except("skip-schedule").permit!.to_h + skip_venue_details = params[:'skip-venue-details'] + skip_venue_details = ActiveRecord::Type::Boolean.new.cast(skip_venue_details) + wcif = params.except("skip-schedule", "skip-venue-details").permit!.to_h wcif = wcif["_json"] || wcif - competition.set_wcif!(wcif, require_user!, skip_schedule: skip_schedule) + competition.set_wcif!(wcif, require_user!, skip_schedule: skip_schedule, skip_venue_details: skip_venue_details) render json: { status: "Successfully saved WCIF", } diff --git a/app/models/competition.rb b/app/models/competition.rb index 81cee3c1711..b6d1bc198a2 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1963,13 +1963,13 @@ def schedule_wcif } end - def set_wcif!(wcif, current_user, skip_schedule: false) + def set_wcif!(wcif, current_user, skip_schedule: false, skip_venue_details: false) JSON::Validator.validate!(Competition.wcif_json_schema, wcif) ActiveRecord::Base.transaction do set_wcif_series!(wcif["series"], current_user) if wcif["series"] set_wcif_events!(wcif["events"], current_user) if wcif["events"] - set_wcif_schedule!(wcif["schedule"], current_user, skip_schedule: skip_schedule) if wcif["schedule"] + set_wcif_schedule!(wcif["schedule"], current_user, skip_schedule: skip_schedule, skip_venue_details: skip_venue_details) if wcif["schedule"] update_persons_wcif!(wcif["persons"], current_user) if wcif["persons"] WcifExtension.update_wcif_extensions!(self, wcif["extensions"]) if wcif["extensions"] set_wcif_competitor_limit!(wcif["competitorLimit"], current_user) if wcif["competitorLimit"] @@ -2134,7 +2134,7 @@ def update_persons_wcif!(wcif_persons, current_user) Assignment.upsert_all(new_assignments) if new_assignments.any? end - def set_wcif_schedule!(wcif_schedule, current_user, skip_schedule: false) + def set_wcif_schedule!(wcif_schedule, current_user, skip_schedule: false, skip_venue_details: false) if wcif_schedule["startDate"] != start_date.strftime("%F") raise WcaExceptions::BadApiParameter.new("Wrong start date for competition") elsif wcif_schedule["numberOfDays"] != number_of_days @@ -2153,7 +2153,7 @@ def set_wcif_schedule!(wcif_schedule, current_user, skip_schedule: false) # using this find instead of ActiveRecord's find_or_create_by avoid several queries # (despite having the association included :() venue = competition_venues.find { |v| v.wcif_id == venue_wcif["id"] } || competition_venues.build - venue.load_wcif!(venue_wcif, skip_schedule: skip_schedule) + venue.load_wcif!(venue_wcif, skip_schedule: skip_schedule, skip_venue_details: skip_venue_details) end self.competition_venues = new_venues diff --git a/app/models/competition_venue.rb b/app/models/competition_venue.rb index 7b8dbc784f6..5c7c95d64dc 100644 --- a/app/models/competition_venue.rb +++ b/app/models/competition_venue.rb @@ -22,11 +22,11 @@ def country Country.find_by_iso2(self.country_iso2) end - def load_wcif!(wcif, skip_schedule: false) - update!(CompetitionVenue.wcif_to_attributes(wcif)) + def load_wcif!(wcif, skip_schedule: false, skip_venue_details: false) + update!(CompetitionVenue.wcif_to_attributes(wcif)) unless skip_venue_details new_rooms = wcif["rooms"].map do |room_wcif| room = venue_rooms.find { |r| r.wcif_id == room_wcif["id"] } || venue_rooms.build - room.load_wcif!(room_wcif, skip_schedule: skip_schedule) + room.load_wcif!(room_wcif, skip_schedule: skip_schedule, skip_venue_details: skip_venue_details) end self.venue_rooms = new_rooms WcifExtension.update_wcif_extensions!(self, wcif["extensions"]) if wcif["extensions"] diff --git a/app/models/venue_room.rb b/app/models/venue_room.rb index 8c337cfb15a..7248c6729ad 100644 --- a/app/models/venue_room.rb +++ b/app/models/venue_room.rb @@ -53,8 +53,8 @@ def self.wcif_json_schema } end - def load_wcif!(wcif, skip_schedule: false) - update!(VenueRoom.wcif_to_attributes(wcif)) + def load_wcif!(wcif, skip_schedule: false, skip_venue_details: false) + update!(VenueRoom.wcif_to_attributes(wcif)) unless skip_venue_details unless skip_schedule new_activities = wcif["activities"].map do |activity_wcif| activity = schedule_activities.find { |a| a.wcif_id == activity_wcif["id"] } || schedule_activities.build From a7e9a8da23aa22f18c3fa30504e237c065a11df2 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 13 Jan 2025 20:59:34 +0900 Subject: [PATCH 31/33] Split PATCH URL config between EditVenues and EditSchedules --- app/webpacker/components/EditSchedule/index.js | 2 +- app/webpacker/components/EditVenues/index.js | 2 +- app/webpacker/lib/requests/routes.js.erb | 2 +- app/webpacker/lib/utils/wcif.js | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/webpacker/components/EditSchedule/index.js b/app/webpacker/components/EditSchedule/index.js index 62417d74984..4ee53f4c494 100644 --- a/app/webpacker/components/EditSchedule/index.js +++ b/app/webpacker/components/EditSchedule/index.js @@ -55,7 +55,7 @@ function EditSchedule({ }; }, [onUnload]); - const { saveWcif, saving } = useSaveWcifAction(); + const { saveWcif, saving } = useSaveWcifAction({ skipVenueDetails: true }); const save = useCallback(() => { saveWcif( diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js index c49805e42a3..3f526aaff4c 100644 --- a/app/webpacker/components/EditVenues/index.js +++ b/app/webpacker/components/EditVenues/index.js @@ -54,7 +54,7 @@ function EditVenues({ }; }, [onUnload]); - const { saveWcif, saving } = useSaveWcifAction(true); + const { saveWcif, saving } = useSaveWcifAction({ skipSchedule: true }); const save = useCallback(() => { saveWcif( diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index a22056b138f..b65bcecc136 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -299,4 +299,4 @@ export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.rou export const paymentTicketUrl = (competitionId, donationIso) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_payment_ticket_path(competition_id: "${competitionId}", donation_iso: "${donationIso}")) %>`; -export const patchWcifUrl = (competitionId, skipSchedule = false) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_update_wcif_path(competition_id: "${competitionId}")) %>?skip-schedule=${skipSchedule}`; +export const patchWcifUrl = (competitionId, patchOpts = {}) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_update_wcif_path(competition_id: "${competitionId}")) %>?skip-schedule=${patchOpts["skipSchedule"]}&skip-venue-details=${patchOpts["skipVenueDetails"]}`; diff --git a/app/webpacker/lib/utils/wcif.js b/app/webpacker/lib/utils/wcif.js index c66fba9e4c5..b76dc934031 100644 --- a/app/webpacker/lib/utils/wcif.js +++ b/app/webpacker/lib/utils/wcif.js @@ -7,7 +7,7 @@ import useSaveAction from '../hooks/useSaveAction'; import { centisecondsToClockFormat } from '../wca-live/attempts'; import { patchWcifUrl } from '../requests/routes.js.erb'; -export function useSaveWcifAction(skipSchedule = false) { +export function useSaveWcifAction(patchOpts = {}) { const { save, saving } = useSaveAction(); const alertWcifError = (err) => { @@ -23,11 +23,11 @@ export function useSaveWcifAction(skipSchedule = false) { options = {}, onError = alertWcifError, ) => { - const url = patchWcifUrl(competitionId, skipSchedule); + const url = patchWcifUrl(competitionId, patchOpts); save(url, wcifData, onSuccess, options, onError); }, - [save, skipSchedule], + [save, patchOpts], ); return { From c198c7b2c8be554f27557746fa11a23984cd49dd Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Tue, 14 Jan 2025 20:30:37 +0900 Subject: [PATCH 32/33] Move patchOptions in frontend to avoid rerender --- app/webpacker/components/EditSchedule/index.js | 6 +++++- app/webpacker/components/EditVenues/index.js | 6 +++++- app/webpacker/lib/utils/wcif.js | 11 ++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/webpacker/components/EditSchedule/index.js b/app/webpacker/components/EditSchedule/index.js index 4ee53f4c494..2439bdd6ec9 100644 --- a/app/webpacker/components/EditSchedule/index.js +++ b/app/webpacker/components/EditSchedule/index.js @@ -19,6 +19,8 @@ import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider' import ConfirmProvider from '../../lib/providers/ConfirmProvider'; import ManageActivities from './ManageActivities'; +const PATCH_OPTIONS = { skipVenueDetails: true }; + function EditSchedule({ wcifEvents, referenceTime, @@ -55,13 +57,15 @@ function EditSchedule({ }; }, [onUnload]); - const { saveWcif, saving } = useSaveWcifAction({ skipVenueDetails: true }); + const { saveWcif, saving } = useSaveWcifAction(); const save = useCallback(() => { saveWcif( competitionId, { schedule: wcifSchedule }, () => dispatch(changesSaved()), + {}, + PATCH_OPTIONS, ); }, [competitionId, dispatch, saveWcif, wcifSchedule]); diff --git a/app/webpacker/components/EditVenues/index.js b/app/webpacker/components/EditVenues/index.js index 3f526aaff4c..1f4167e748d 100644 --- a/app/webpacker/components/EditVenues/index.js +++ b/app/webpacker/components/EditVenues/index.js @@ -19,6 +19,8 @@ import Store, { useDispatch, useStore } from '../../lib/providers/StoreProvider' import ConfirmProvider from '../../lib/providers/ConfirmProvider'; import ManageVenues from './ManageVenues'; +const PATCH_OPTIONS = { skipSchedule: true }; + function EditVenues({ countryZones, referenceTime, @@ -54,13 +56,15 @@ function EditVenues({ }; }, [onUnload]); - const { saveWcif, saving } = useSaveWcifAction({ skipSchedule: true }); + const { saveWcif, saving } = useSaveWcifAction(); const save = useCallback(() => { saveWcif( competitionId, { schedule: wcifSchedule }, () => dispatch(changesSaved()), + {}, + PATCH_OPTIONS, ); }, [competitionId, dispatch, saveWcif, wcifSchedule]); diff --git a/app/webpacker/lib/utils/wcif.js b/app/webpacker/lib/utils/wcif.js index b76dc934031..e7da6f73c68 100644 --- a/app/webpacker/lib/utils/wcif.js +++ b/app/webpacker/lib/utils/wcif.js @@ -7,7 +7,7 @@ import useSaveAction from '../hooks/useSaveAction'; import { centisecondsToClockFormat } from '../wca-live/attempts'; import { patchWcifUrl } from '../requests/routes.js.erb'; -export function useSaveWcifAction(patchOpts = {}) { +export function useSaveWcifAction() { const { save, saving } = useSaveAction(); const alertWcifError = (err) => { @@ -20,14 +20,15 @@ export function useSaveWcifAction(patchOpts = {}) { competitionId, wcifData, onSuccess, - options = {}, + fetchOptions = {}, + patchWcifOptions = {}, onError = alertWcifError, ) => { - const url = patchWcifUrl(competitionId, patchOpts); + const url = patchWcifUrl(competitionId, patchWcifOptions); - save(url, wcifData, onSuccess, options, onError); + save(url, wcifData, onSuccess, fetchOptions, onError); }, - [save, patchOpts], + [save], ); return { From b2b1d110d8310ad5457cd40a59e68ee65647e084 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Tue, 14 Jan 2025 21:02:40 +0900 Subject: [PATCH 33/33] Fix 'undefined' parameter in WCIF patch --- app/controllers/api/v0/competitions_controller.rb | 6 +++--- app/webpacker/lib/requests/routes.js.erb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v0/competitions_controller.rb b/app/controllers/api/v0/competitions_controller.rb index 7d8d41545e3..3605cb59309 100644 --- a/app/controllers/api/v0/competitions_controller.rb +++ b/app/controllers/api/v0/competitions_controller.rb @@ -166,11 +166,11 @@ def show_wcif_public def update_wcif competition = competition_from_params require_can_manage!(competition) - skip_schedule = params[:'skip-schedule'] + skip_schedule = params[:skipSchedule] skip_schedule = ActiveRecord::Type::Boolean.new.cast(skip_schedule) - skip_venue_details = params[:'skip-venue-details'] + skip_venue_details = params[:skipVenueDetails] skip_venue_details = ActiveRecord::Type::Boolean.new.cast(skip_venue_details) - wcif = params.except("skip-schedule", "skip-venue-details").permit!.to_h + wcif = params.except("skipSchedule", "skipVenueDetails").permit!.to_h wcif = wcif["_json"] || wcif competition.set_wcif!(wcif, require_user!, skip_schedule: skip_schedule, skip_venue_details: skip_venue_details) render json: { diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index b65bcecc136..e53a5aa572f 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -299,4 +299,4 @@ export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.rou export const paymentTicketUrl = (competitionId, donationIso) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_payment_ticket_path(competition_id: "${competitionId}", donation_iso: "${donationIso}")) %>`; -export const patchWcifUrl = (competitionId, patchOpts = {}) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_update_wcif_path(competition_id: "${competitionId}")) %>?skip-schedule=${patchOpts["skipSchedule"]}&skip-venue-details=${patchOpts["skipVenueDetails"]}`; +export const patchWcifUrl = (competitionId, patchOpts = {}) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v0_competition_update_wcif_path(competition_id: "${competitionId}")) %>?${jsonToQueryString(patchOpts)}`;