refactor: simplify ux for sheet import

This commit is contained in:
Carlos Valente
2025-11-02 07:59:29 +01:00
committed by Carlos Valente
parent 9dfc8083f0
commit becd734ea8
6 changed files with 179 additions and 9 deletions
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { CustomFields, Rundown } from 'ontime-types';
import { getFirstEventNormal, getLastEventNormal, millisToString } from 'ontime-utils';
import Button from '../../../../../common/components/buttons/Button';
import useRundown from '../../../../../common/hooks-query/useRundown';
@@ -43,6 +44,9 @@ export default function ImportReview(props: ImportReviewProps) {
onFinished();
};
const { firstEvent } = getFirstEventNormal(rundown.entries, rundown.flatOrder);
const { lastEvent } = getLastEventNormal(rundown.entries, rundown.flatOrder);
return (
<Panel.Section>
<Panel.Title>
@@ -56,6 +60,24 @@ export default function ImportReview(props: ImportReviewProps) {
</Button>
</Panel.InlineElements>
</Panel.Title>
<Panel.ListGroup>
<Panel.ListItem>
<b>Title</b> {rundown.title}
</Panel.ListItem>
<Panel.ListItem>
<b>Number of entries</b> {rundown.flatOrder.length}
</Panel.ListItem>
{firstEvent && (
<Panel.ListItem>
<b>Start Time</b> {millisToString(firstEvent.timeStart)}
</Panel.ListItem>
)}
{lastEvent && (
<Panel.ListItem>
<b>End Time</b> {millisToString(lastEvent.timeEnd)}
</Panel.ListItem>
)}
</Panel.ListGroup>
<PreviewSpreadsheet rundown={rundown} customFields={customFields} />
</Panel.Section>
);
@@ -5,6 +5,7 @@ import { checkRegex, ImportMap } from 'ontime-utils';
import Button from '../../../../../../common/components/buttons/Button';
import IconButton from '../../../../../../common/components/buttons/IconButton';
import Info from '../../../../../../common/components/info/Info';
import Input from '../../../../../../common/components/input/input/Input';
import Select from '../../../../../../common/components/select/Select';
import Tooltip from '../../../../../../common/components/tooltip/Tooltip';
@@ -136,6 +137,10 @@ export default function ImportMapForm({
</Button>
</Panel.InlineElements>
</Panel.Title>
<Info>
Match your spreadsheet columns to Ontime fields. <br />
You can also add Custom Fields by providing a name for Ontime and the spreadsheet column name.
</Info>
<Panel.Table>
<thead>
<tr>
@@ -18,9 +18,7 @@ function booleanToText(value?: boolean) {
return value ? 'Yes' : undefined;
}
export default function PreviewRundown(props: PreviewRundownProps) {
const { rundown, customFields } = props;
export default function PreviewRundown({ rundown, customFields }: PreviewRundownProps) {
// we only count Ontime Events which are 1 based in client
let eventIndex = 0;
@@ -59,9 +57,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
const colour = entry.colour ? getAccessibleColour(entry.colour) : {};
return (
<tr key={entry.id}>
<td className={style.center}>
<Tag>-</Tag>
</td>
<td /> {/** Index */}
<td className={style.center}>
<Tag>{entry.type}</Tag>
</td>
@@ -0,0 +1,124 @@
export const simpleTemplate = [
['ⓘ Notes on import', '', '', '', '', '', 'ⓘ How to use'],
[
'Ontime will import this spreasheet as a rundown in your project',
'',
'',
'',
'',
'',
'1. Make your own copy of the spreadsheet by either File -> Make a copy or File -> Download -> Microsoft Excel',
],
[
'- Each spreadsheet can only contain one rundown',
'',
'',
'',
'',
'',
'2. Delete the table header with the Notes on import and How to use instructions',
],
[
'- You can have as many columns as necessary, Ontime only imports the columns you request',
'',
'',
'',
'',
'',
'3. Remove any columns that are not necessary to your workflow',
],
[
'- The names of the worksheet and columns are given in Ontime on import',
'',
'',
'',
'',
'',
'See documentation for spreadsheet import',
'',
'https://docs.getontime.no/features/import-spreadsheet/',
],
[
'- Ontime creates an entry for every row on the table with a known Timer Type (see note on column header)',
'',
'',
'',
'',
'',
'See documentation for Google Sheet import',
'',
'https://docs.getontime.no/features/import-spreadsheet-gsheet/',
],
['- All fields in a row are optional, Ontime will fill in with default data as needed'],
['ⓘ Hover over note tags to see additional help on column data'],
[
'',
'',
'',
'Note ---------------------------------------------',
'',
'',
'',
'Note',
'Note --------------------------------------------------------',
],
['Cue', 'Title', 'Colour', 'Time Start', 'Time End', 'Duration', 'Note', 'Timer Type', 'Video Notes'],
[
'Demo',
'Clear all, or Create New Project to start fresh',
'#9d9d9d',
'',
'',
'',
'Moderator - Emma Thompson\n' +
'\n' +
'Speakers\n' +
'- Liam Carter + Sophia Patel\n' +
'- Ethan Brooks\n' +
'- Lucas Bennett',
'milestone',
],
['', 'Morning Sessions', '#339E4E', '', '', '', '', 'group-start'],
[
'1',
'Pre-show Countdown',
'#77C785',
'10:00:00',
'11:00:00',
'',
'Music plays, holding slide on screens',
'count-down',
'Camera + PowerPoint on stream\nPowerPoint on screens',
],
['Standby', '10:45 - Presenters ready side stage', '#A790F5', '', '', '', '', 'milestone'],
['1.1', 'Welcome', '#FFCC78', '', '', '00:10:00', '', 'count-down', 'Cameras on stream\nPowerPoint on screens'],
[
'1.2',
'Session 1',
'#77C785',
'',
'',
'00:50:00',
'',
'count-down',
'Camera + PowerPoint on stream\nPowerPoint on screens',
],
['House', '11:30 - House staff setup lunch in lobby', '#A790F5', '', '', '', '', 'milestone'],
['', 'Lunch', '#3E75E8', '', '', '', '', 'group-start'],
['2.1', 'Lunch / Countdown to next session', '#779BE7', '12:00:00', '13:00:00', '', '', 'count-down'],
['', 'Afternoon Sessions', '#339E4E', '', '', '', '', 'group-start'],
['Standby', '12:45 - Presenters ready side stage', '#A790F5', '', '', '', '', 'milestone'],
[
'3.2',
'Session 2',
'#77C785',
'13:00:00',
'',
'00:50:00',
'',
'count-down',
'Camera + PPT + Video on stream\n' + 'PowerPoint + Video on screens\n' + '\n' + 'Video file: Session2.mp4',
],
['3.2', 'Wrap up', '#FFCC78', '', '', '00:10:00', '', 'count-down', 'Holding slide on screens'],
['Strike', '14:00 - AV & Room strike', '#A790F5', '', '', '', '', 'milestone'],
];
@@ -1,12 +1,34 @@
import { CustomFields, OntimeEvent, OntimeGroup, SupportedEntry, TimerType } from 'ontime-types';
import { ImportMap, MILLIS_PER_MINUTE } from 'ontime-utils';
import { ImportMap, MILLIS_PER_MINUTE, millisToString } from 'ontime-utils';
import { parseExcel } from '../excel.parser.js';
import { dataFromExcelTemplate } from './mockData.js';
import { simpleTemplate } from '../__mocks__/simpleTemplate.js';
describe('parseExcel()', () => {
it('parses the example file', () => {
it('parses the simple template file', () => {
const result = parseExcel(simpleTemplate, {});
expect(result.rundown.flatOrder.length).toBe(14);
expect(result.rundown.order.length).toBe(4);
const firstEvent = result.rundown.entries[result.rundown.flatOrder[2]];
expect(firstEvent).toMatchObject({
type: SupportedEntry.Event,
title: 'Pre-show Countdown',
});
expect(millisToString((firstEvent as OntimeEvent).timeStart)).toBe('10:00:00');
const lastEvent = result.rundown.entries[result.rundown.flatOrder[12]];
expect(lastEvent).toMatchObject({
type: SupportedEntry.Event,
title: 'Wrap up',
});
// the excel step does not relate elements yet
expect(millisToString((lastEvent as OntimeEvent).timeEnd)).toBe('00:00:00');
});
it('parses an import map with only custom fields', () => {
// partial import map with only custom fields
const importMap = {
custom: {
@@ -212,7 +212,7 @@ export const parseExcel = (
return;
}
//and fall through to treat it as an event
// after group and milestones we only have events remaining
const event = {
...entry,
custom: { ...entryCustomFields },
@@ -223,6 +223,7 @@ export const parseExcel = (
event.timerType = TimerType.CountDown;
}
// we link all events unless user specifies otherwise
if (entry.linkStart === undefined) {
event.linkStart = true;
}