mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
feat: excel import (#527)
* refactor: simplify excel import * chore: add external deepmerge utility * chore: create import map utilities * chore: increase size limits on uploads * style: small presentation tweaks * feat: resolve times on excel import, refs #508
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { MutableRefObject } from 'react';
|
||||
import { ExcelImportMap } from 'ontime-utils';
|
||||
|
||||
import ImportMapTable, { type TableEntry } from './ImportMapTable';
|
||||
|
||||
import style from '../UploadModal.module.scss';
|
||||
|
||||
interface ExcelFileOptionsProps {
|
||||
optionsRef: MutableRefObject<ExcelImportMap>;
|
||||
updateOptions: <T extends keyof ExcelImportMap>(field: T, value: ExcelImportMap[T]) => void;
|
||||
}
|
||||
|
||||
export default function ExcelFileOptions(props: ExcelFileOptionsProps) {
|
||||
const { optionsRef, updateOptions } = props;
|
||||
|
||||
const worksheet: TableEntry[] = [{ label: 'Worksheet', title: 'worksheet', value: optionsRef.current.worksheet }];
|
||||
|
||||
const timings: TableEntry[] = [
|
||||
{ label: 'Start time', title: 'timeStart', value: optionsRef.current.timeStart },
|
||||
{ label: 'End Time', title: 'timeEnd', value: optionsRef.current.timeEnd },
|
||||
{ label: 'Duration', title: 'duration', value: optionsRef.current.duration },
|
||||
];
|
||||
|
||||
const titles: TableEntry[] = [
|
||||
{ label: 'Cue', title: 'cue', value: optionsRef.current.cue },
|
||||
{ label: 'Colour', title: 'colour', value: optionsRef.current.colour },
|
||||
{ label: 'Title', title: 'title', value: optionsRef.current.title },
|
||||
{ label: 'Presenter', title: 'presenter', value: optionsRef.current.presenter },
|
||||
{ label: 'Subtitle', title: 'subtitle', value: optionsRef.current.subtitle },
|
||||
{ label: 'Note', title: 'note', value: optionsRef.current.note },
|
||||
];
|
||||
|
||||
const options: TableEntry[] = [
|
||||
{ label: 'Is Public', title: 'isPublic', value: optionsRef.current.isPublic },
|
||||
{ label: 'Skip', title: 'skip', value: optionsRef.current.skip },
|
||||
{ label: 'Timer Type', title: 'timerType', value: optionsRef.current.timerType },
|
||||
{ label: 'End Action', title: 'endAction', value: optionsRef.current.endAction },
|
||||
];
|
||||
|
||||
const userFields: TableEntry[] = [
|
||||
{ label: 'User 0', title: 'user0', value: optionsRef.current.user0 },
|
||||
{ label: 'User 1', title: 'user1', value: optionsRef.current.user1 },
|
||||
{ label: 'User 2', title: 'user2', value: optionsRef.current.user2 },
|
||||
{ label: 'User 3', title: 'user3', value: optionsRef.current.user3 },
|
||||
{ label: 'User 4', title: 'user4', value: optionsRef.current.user4 },
|
||||
{ label: 'User 5', title: 'user5', value: optionsRef.current.user5 },
|
||||
{ label: 'User 6', title: 'user6', value: optionsRef.current.user6 },
|
||||
{ label: 'User 7', title: 'user7', value: optionsRef.current.user7 },
|
||||
{ label: 'User 8', title: 'user8', value: optionsRef.current.user8 },
|
||||
{ label: 'User 9', title: 'user9', value: optionsRef.current.user9 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={style.uploadOptions}>
|
||||
<div className={style.twoEqualColumn}>
|
||||
<ImportMapTable title='Sheet settings' fields={worksheet} handleOnChange={updateOptions} />
|
||||
</div>
|
||||
|
||||
<div className={style.twoEqualColumn}>
|
||||
<ImportMapTable title='Timings' fields={timings} handleOnChange={updateOptions} />
|
||||
<ImportMapTable title='Options' fields={options} handleOnChange={updateOptions} />
|
||||
</div>
|
||||
|
||||
<div className={style.twoEqualColumn}>
|
||||
<ImportMapTable title='Titles' fields={titles} handleOnChange={updateOptions} />
|
||||
<ImportMapTable title='User Fields' fields={userFields} handleOnChange={updateOptions} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
@use '../../../../theme/v2Styles' as *;
|
||||
@use '../../../../theme/ontimeColours' as *;
|
||||
|
||||
.importTable {
|
||||
margin: 0.5rem;
|
||||
height: fit-content;
|
||||
|
||||
thead {
|
||||
color: $gray-500;
|
||||
text-transform: uppercase;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: $gray-50;
|
||||
}
|
||||
|
||||
tbody {
|
||||
td {
|
||||
max-width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
min-width: 6em;
|
||||
font-size: $inner-section-text-size;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { ExcelImportMap } from 'ontime-utils';
|
||||
|
||||
import style from './ImportMapTable.module.scss';
|
||||
|
||||
export type TableEntry = { label: string; title: keyof ExcelImportMap; value: string };
|
||||
|
||||
interface ImportMapTableProps {
|
||||
title: string;
|
||||
fields: TableEntry[];
|
||||
handleOnChange: (field: keyof ExcelImportMap, value: string) => void;
|
||||
}
|
||||
|
||||
export default function ImportMapTable(props: ImportMapTableProps) {
|
||||
const { title, fields, handleOnChange } = props;
|
||||
|
||||
return (
|
||||
<table className={style.importTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={2}>{title}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{fields.map((field) => {
|
||||
return (
|
||||
<tr key={field.title}>
|
||||
<td className={style.label}>
|
||||
<label htmlFor={field.title}>{field.label}</label>
|
||||
</td>
|
||||
<td className={style.input}>
|
||||
<Input
|
||||
id={field.title}
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
maxLength={25}
|
||||
defaultValue={field.value}
|
||||
placeholder='Use default column name'
|
||||
onBlur={(event) => {
|
||||
handleOnChange(field.title, event.target.value);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { MutableRefObject } from 'react';
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
|
||||
import { ProjectFileImportOptions } from '../../../../common/api/ontimeApi';
|
||||
import ModalSplitInput from '../../ModalSplitInput';
|
||||
|
||||
import style from '../UploadModal.module.scss';
|
||||
|
||||
interface OntimeFileOptionsProps {
|
||||
optionsRef: MutableRefObject<Partial<ProjectFileImportOptions>>;
|
||||
updateOptions: <T extends keyof ProjectFileImportOptions>(field: T, value: ProjectFileImportOptions[T]) => void;
|
||||
}
|
||||
|
||||
export default function OntimeFileOptions(props: OntimeFileOptionsProps) {
|
||||
const { optionsRef, updateOptions } = props;
|
||||
|
||||
return (
|
||||
<div className={style.uploadOptions}>
|
||||
<span className={style.title}>Import options</span>
|
||||
<ModalSplitInput field='' title='Only import rundown' description='All other project options will be kept'>
|
||||
<Switch
|
||||
variant='ontime-on-light'
|
||||
onChange={(e) => {
|
||||
updateOptions('onlyRundown', e.target.checked);
|
||||
}}
|
||||
defaultChecked={Boolean(optionsRef.current.onlyRundown)}
|
||||
/>
|
||||
</ModalSplitInput>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user