From 51e0676fb7c9f208df893c9669e4b88baf239928 Mon Sep 17 00:00:00 2001 From: "philip.cline" Date: Tue, 21 Nov 2023 15:03:50 -0500 Subject: [PATCH 1/4] feat(Timetable): fix pasting p1 --- .../components/timetable/EditableCell.js | 22 ++++---- .../components/timetable/TimetableGrid.js | 53 +++++++++++-------- lib/editor/util/timetable.js | 15 ++++-- 3 files changed, 54 insertions(+), 36 deletions(-) diff --git a/lib/editor/components/timetable/EditableCell.js b/lib/editor/components/timetable/EditableCell.js index 2f23f3cb2..eb9704599 100644 --- a/lib/editor/components/timetable/EditableCell.js +++ b/lib/editor/components/timetable/EditableCell.js @@ -5,7 +5,7 @@ import moment from 'moment' import * as tripActions from '../../actions/trip' import {secondsAfterMidnightToHHMM} from '../../../common/util/gtfs' -import { isTimeFormat } from '../../util/timetable' +import { isTimeFormat, parseCellValue } from '../../util/timetable' import type {TimetableColumn} from '../../../types' import type {EditorValidationIssue} from '../../util/validation' @@ -66,8 +66,6 @@ export default class EditableCell extends Component { }) } - cellInput = null - /** * The component may receive data from a save event or * editing can be changed by a change in the activeCell in the TimetableGrid @@ -157,8 +155,6 @@ export default class EditableCell extends Component { offsetScrollCol(-1) return case 38: // up - this.save() - break case 40: // down this.save() break @@ -206,6 +202,14 @@ export default class EditableCell extends Component { // Ensure that only a positive integer value can be set. const value = +data this._handleSave(value) + } else if (isTimeFormat(column.type)) { + if (typeof data !== 'string') return this.cancel() + const parsedValue = parseCellValue(data, column) + if (parsedValue !== null) { + this._handleSave(parsedValue) + } else { + this.cancel() + } } else { if (typeof data !== 'string') return this.cancel() const duration = moment.duration(data) @@ -238,11 +242,8 @@ export default class EditableCell extends Component { ? String.fromCharCode(13) : undefined const rows = text.split(rowDelimiter) - const rowsAndColumns = [] - // Split each row into columns - for (let i = 0; i < rows.length; i++) { - rowsAndColumns.push(rows[i].split(String.fromCharCode(9))) - } + // Remove carriage return characters (/r) from rows to handle pasted data from Excel + const rowsAndColumns = rows.map(row => row.split(String.fromCharCode(9)).map(cellValue => cellValue.replace(/\r/g, ''))) if (rowsAndColumns.length > 1 || rowsAndColumns[0].length > 1) { this.cancel() @@ -303,6 +304,7 @@ export default class EditableCell extends Component { onPaste={this.handlePaste} placeholder={placeholder || ''} readOnly={!isEditing} + ref='inputCell' type='text' /> :
{ addNewRow, columns, data, - setActiveCell, + // setActiveCell, hideDepartureTimes, updateCellValue, updateScroll } = this.props let activeRow = rowIndex let activeCol = colIndex - // iterate over rows in pasted selection + // Iterate over rows in pasted selection for (var i = 0; i < pastedRows.length; i++) { activeRow = rowIndex + i - // construct new row if it doesn't exist - if (typeof data[i + rowIndex] === 'undefined') { + // Construct new row if it doesn't exist + if (typeof data[activeRow] === 'undefined') { addNewRow() } - // iterate over number of columns in pasted selection + // Iterate over number of columns in pasted selection for (var j = 0; j < pastedRows[0].length; j++) { activeCol = colIndex + j - const path = `${rowIndex + i}.${columns[colIndex + j].key}` - const value = parseTime(pastedRows[i][j]) - updateCellValue({value, rowIndex: rowIndex + i, key: path}) - // if departure times are hidden, paste into adjacent time column - const adjacentPath = `${rowIndex + i}.${columns[colIndex + j + 2].key}` - if ( - hideDepartureTimes && - isTimeFormat(columns[colIndex + j].type) && - typeof objectPath.get(data, adjacentPath) !== 'undefined' - ) { - updateCellValue({value, rowIndex: rowIndex + i, key: adjacentPath}) + const col = columns[activeCol] + const path = `${activeRow}.${col.key}` + const originalValue = objectPath.get(data[activeRow], col.key) + const value = parseCellValue(pastedRows[i][j], col) + if (value !== null) { + const finalValue = value + updateCellValue({ value: finalValue, rowIndex: activeRow, key: path }) + // If departure times are hidden, paste into adjacent time column + const adjacentPath = `${activeRow}.${columns[activeCol + 2].key}` + if ( + hideDepartureTimes && + isTimeFormat(col.type) && + typeof objectPath.get(data, adjacentPath) !== 'undefined' + ) { + updateCellValue({ value: finalValue, rowIndex: activeRow, key: adjacentPath }) + } + } else { + // If the value is rejected, keep the original value + updateCellValue({ value: originalValue, rowIndex: activeRow, key: path }) } } } - setActiveCell(`${activeRow}-${activeCol}`) + // Prevent the active cell from being set to the last cell in the pasted selection + // if (activeRow !== rowIndex + pastedRows.length - 1) { + // setActiveCell(`${activeRow}-${activeCol}`) + // } updateScroll(activeRow, activeCol) } diff --git a/lib/editor/util/timetable.js b/lib/editor/util/timetable.js index 7fc1bd5d2..892d35b5e 100644 --- a/lib/editor/util/timetable.js +++ b/lib/editor/util/timetable.js @@ -40,12 +40,17 @@ export function getHeaderColumns ( return columns.filter(c => c.type !== 'DEPARTURE_TIME') } -export function parseTime (timeString: string) { +export function parseCellValue (timeString: string, col: TimetableColumn) { const date = moment().startOf('day').format('YYYY-MM-DD') - return moment(date + 'T' + timeString, TIMETABLE_FORMATS).diff( - date, - 'seconds' - ) + const parsedDate = moment(date + 'T' + timeString, TIMETABLE_FORMATS, true) + if (isTimeFormat(col.type)) { + if (!parsedDate.isValid()) { + alert(`Please enter a valid time format`) + return null + } + return moment(date + 'T' + timeString, TIMETABLE_FORMATS).diff(date, 'seconds') + } + return timeString } export const LEFT_COLUMN_WIDTH = 30 From 1bd69b894338f15b707a20ae73155b0bd3eddebc Mon Sep 17 00:00:00 2001 From: "philip.cline" Date: Thu, 23 Nov 2023 09:45:05 -0500 Subject: [PATCH 2/4] refactor(Timetable pasting): clean up branch --- lib/editor/components/timetable/EditableCell.js | 1 - lib/editor/components/timetable/TimetableGrid.js | 5 ----- 2 files changed, 6 deletions(-) diff --git a/lib/editor/components/timetable/EditableCell.js b/lib/editor/components/timetable/EditableCell.js index eb9704599..baf3e3916 100644 --- a/lib/editor/components/timetable/EditableCell.js +++ b/lib/editor/components/timetable/EditableCell.js @@ -304,7 +304,6 @@ export default class EditableCell extends Component { onPaste={this.handlePaste} placeholder={placeholder || ''} readOnly={!isEditing} - ref='inputCell' type='text' /> :
{ addNewRow, columns, data, - // setActiveCell, hideDepartureTimes, updateCellValue, updateScroll @@ -410,10 +409,6 @@ export default class TimetableGrid extends Component { } } } - // Prevent the active cell from being set to the last cell in the pasted selection - // if (activeRow !== rowIndex + pastedRows.length - 1) { - // setActiveCell(`${activeRow}-${activeCol}`) - // } updateScroll(activeRow, activeCol) } From 7c4492a0f0a96570fd64d1f87de9ed664485e1f3 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 23 Nov 2023 16:08:55 +0100 Subject: [PATCH 3/4] feat(Timetable): fix pasting p2 --- lib/editor/components/timetable/TimetableGrid.js | 4 +++- lib/editor/util/timetable.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/editor/components/timetable/TimetableGrid.js b/lib/editor/components/timetable/TimetableGrid.js index 756ce6000..bbe2e7340 100644 --- a/lib/editor/components/timetable/TimetableGrid.js +++ b/lib/editor/components/timetable/TimetableGrid.js @@ -377,6 +377,7 @@ export default class TimetableGrid extends Component { } = this.props let activeRow = rowIndex let activeCol = colIndex + let errorShown = false // Iterate over rows in pasted selection for (var i = 0; i < pastedRows.length; i++) { activeRow = rowIndex + i @@ -390,7 +391,7 @@ export default class TimetableGrid extends Component { const col = columns[activeCol] const path = `${activeRow}.${col.key}` const originalValue = objectPath.get(data[activeRow], col.key) - const value = parseCellValue(pastedRows[i][j], col) + const value = parseCellValue(pastedRows[i][j], col, errorShown) if (value !== null) { const finalValue = value updateCellValue({ value: finalValue, rowIndex: activeRow, key: path }) @@ -406,6 +407,7 @@ export default class TimetableGrid extends Component { } else { // If the value is rejected, keep the original value updateCellValue({ value: originalValue, rowIndex: activeRow, key: path }) + errorShown = true } } } diff --git a/lib/editor/util/timetable.js b/lib/editor/util/timetable.js index 892d35b5e..e687d7f6c 100644 --- a/lib/editor/util/timetable.js +++ b/lib/editor/util/timetable.js @@ -40,12 +40,12 @@ export function getHeaderColumns ( return columns.filter(c => c.type !== 'DEPARTURE_TIME') } -export function parseCellValue (timeString: string, col: TimetableColumn) { +export function parseCellValue (timeString: string, col: TimetableColumn, errorShown?: boolean) { const date = moment().startOf('day').format('YYYY-MM-DD') const parsedDate = moment(date + 'T' + timeString, TIMETABLE_FORMATS, true) if (isTimeFormat(col.type)) { if (!parsedDate.isValid()) { - alert(`Please enter a valid time format`) + if (errorShown !== true) alert(`Please enter a valid time format`) return null } return moment(date + 'T' + timeString, TIMETABLE_FORMATS).diff(date, 'seconds') From da5e9075748cbdad72181584947e5ee960e8bbed Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 27 Nov 2023 14:31:01 -0500 Subject: [PATCH 4/4] fix duplicate save alert --- lib/editor/components/timetable/EditableCell.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/editor/components/timetable/EditableCell.js b/lib/editor/components/timetable/EditableCell.js index baf3e3916..6f0e8624c 100644 --- a/lib/editor/components/timetable/EditableCell.js +++ b/lib/editor/components/timetable/EditableCell.js @@ -192,6 +192,7 @@ export default class EditableCell extends Component { } save () { + this.cancel() const {column} = this.props const {data} = this.state // for non-time rendering