mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-06 16:03:52 +00:00
add ExcelImport
This commit is contained in:
@@ -306,21 +306,16 @@ export const postWorksheet = async (id: string, worksheet: string) => {
|
||||
/**
|
||||
* @description STEP 5
|
||||
*/
|
||||
export const postPreviewSheet = async (id: string, worksheet: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-preview`, {
|
||||
id,
|
||||
worksheet,
|
||||
});
|
||||
export const postPreviewSheet = async (id: string, options: ExcelImportMap) => {
|
||||
console.log(options);
|
||||
const response = await axios.post(`${ontimeURL}/sheet/pull`, { id, options });
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 5
|
||||
*/
|
||||
export const postPushSheet = async (id: string, worksheet: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-push`, {
|
||||
id,
|
||||
worksheet,
|
||||
});
|
||||
export const postPushSheet = async (id: string, options: ExcelImportMap) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-push`, { id, options });
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types';
|
||||
import { defaultExcelImportMap, ExcelImportMap } from 'ontime-utils';
|
||||
|
||||
import { PROJECT_DATA, RUNDOWN, USERFIELDS } from '../../../common/api/apiConstants';
|
||||
import { maybeAxiosError } from '../../../common/api/apiUtils';
|
||||
@@ -37,6 +38,7 @@ import { userFieldsPlaceholder } from '../../../common/models/UserFields';
|
||||
import { openLink } from '../../../common/utils/linkUtils';
|
||||
import ModalLink from '../ModalLink';
|
||||
import PreviewExcel from '../upload-modal/preview/PreviewExcel';
|
||||
import ExcelFileOptions from '../upload-modal/upload-options/ExcelFileOptions';
|
||||
|
||||
import Step from './Step';
|
||||
|
||||
@@ -58,6 +60,9 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
const [worksheet, setWorksheet] = useState('');
|
||||
const [worksheetOptions, setWorksheetOptions] = useState(new Array<string>());
|
||||
|
||||
const [direction, setDirection] = useState('none');
|
||||
const excelFileOptions = useRef<ExcelImportMap>(defaultExcelImportMap);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [state, setState] = useState({
|
||||
@@ -70,6 +75,7 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setDirection('none');
|
||||
testClientSecrect();
|
||||
if (state.clientSecret.complet) testAuthentication();
|
||||
if (state.authenticate.complet) testSheetId();
|
||||
@@ -190,6 +196,7 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
|
||||
//SETP-4 Select Worksheet
|
||||
const testWorksheet = (value: string) => {
|
||||
excelFileOptions.current.worksheet = value;
|
||||
setWorksheet(value);
|
||||
postWorksheet(id, worksheet)
|
||||
.then(() => {
|
||||
@@ -202,16 +209,32 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
};
|
||||
|
||||
//SETP-5 Upload / Download
|
||||
const updateExcelFileOptions = <T extends keyof ExcelImportMap>(field: T, value: ExcelImportMap[T]) => {
|
||||
if (excelFileOptions.current[field] !== value) {
|
||||
excelFileOptions.current = { ...excelFileOptions.current, [field]: value };
|
||||
}
|
||||
};
|
||||
|
||||
const handlePullData = () => {
|
||||
postPreviewSheet(id, worksheet).then((data) => {
|
||||
setProject(data.project);
|
||||
setRundown(data.rundown);
|
||||
setUserFields(data.userFields);
|
||||
});
|
||||
postPreviewSheet(id, excelFileOptions.current)
|
||||
.then((data) => {
|
||||
setProject(data.project);
|
||||
setRundown(data.rundown);
|
||||
setUserFields(data.userFields);
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = maybeAxiosError(error);
|
||||
setDirection('none');
|
||||
setState({ ...state, pullPush: { complet: false, message } });
|
||||
});
|
||||
};
|
||||
|
||||
const handlePushData = () => {
|
||||
postPushSheet(id, worksheet);
|
||||
postPushSheet(id, excelFileOptions.current).catch((error) => {
|
||||
const message = maybeAxiosError(error);
|
||||
setDirection('none');
|
||||
setState({ ...state, pullPush: { complet: false, message } });
|
||||
});
|
||||
};
|
||||
|
||||
//GET preview
|
||||
@@ -266,119 +289,125 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
</div>
|
||||
</Alert>
|
||||
{!rundown ? (
|
||||
<>
|
||||
<Step
|
||||
title='1 - Upload OAuth 2.0 Client ID'
|
||||
completed={state.clientSecret.complet}
|
||||
disabled={false}
|
||||
error={state.clientSecret.message}
|
||||
>
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
type='file'
|
||||
onChange={handleFile}
|
||||
accept='.json'
|
||||
data-testid='file-input'
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' onClick={handleClick}>
|
||||
{state.clientSecret.complet ? 'Reupload Client ID' : 'Upload Client ID'}
|
||||
</Button>
|
||||
<Button size='sm' variant='ontime-ghosted-on-light' onClick={testClientSecrect}>
|
||||
Retry Client ID
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='2 - Authenticate with Google'
|
||||
completed={state.authenticate.complet}
|
||||
disabled={!state.clientSecret.complet}
|
||||
error={state.authenticate.message}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-subtle-on-light'
|
||||
onClick={handleAuthenticate}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Authenticate
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-ghosted-on-light'
|
||||
onClick={testAuthentication}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Retry Connection
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='3 - Add Document ID'
|
||||
completed={state.id.complet}
|
||||
disabled={!state.authenticate.complet}
|
||||
error={state.id.message}
|
||||
>
|
||||
<HStack>
|
||||
<Input
|
||||
type='text'
|
||||
size='sm'
|
||||
variant='ontime-filled-on-light'
|
||||
disabled={!state.authenticate.complet}
|
||||
value={id}
|
||||
onChange={(event) => setSheetId(event.target.value)}
|
||||
/>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' padding='0 2em' onClick={testSheetId}>
|
||||
Connect
|
||||
</Button>
|
||||
</HStack>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='4 - Select Worksheet to import'
|
||||
completed={state.worksheet.complet}
|
||||
disabled={worksheetOptions.length == 0}
|
||||
>
|
||||
<Select
|
||||
size='sm'
|
||||
disabled={worksheetOptions.length == 0}
|
||||
placeholder='Select a worksheet'
|
||||
onChange={(event) => testWorksheet(event.target.value)}
|
||||
value={worksheet}
|
||||
direction === 'up' ? (
|
||||
<div>{direction}</div>
|
||||
) : direction === 'down' ? (
|
||||
<ExcelFileOptions optionsRef={excelFileOptions} updateOptions={updateExcelFileOptions} />
|
||||
) : (
|
||||
<>
|
||||
<Step
|
||||
title='1 - Upload OAuth 2.0 Client ID'
|
||||
completed={state.clientSecret.complet}
|
||||
disabled={false}
|
||||
error={state.clientSecret.message}
|
||||
>
|
||||
{worksheetOptions.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{value}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Step>
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
style={{ display: 'none' }}
|
||||
type='file'
|
||||
onChange={handleFile}
|
||||
accept='.json'
|
||||
data-testid='file-input'
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' onClick={handleClick}>
|
||||
{state.clientSecret.complet ? 'Reupload Client ID' : 'Upload Client ID'}
|
||||
</Button>
|
||||
<Button size='sm' variant='ontime-ghosted-on-light' onClick={testClientSecrect}>
|
||||
Retry Client ID
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step title='5 - Upload / Download rundown' completed={false} disabled={!state.worksheet.complet}>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={handlePushData}
|
||||
<Step
|
||||
title='2 - Authenticate with Google'
|
||||
completed={state.authenticate.complet}
|
||||
disabled={!state.clientSecret.complet}
|
||||
error={state.authenticate.message}
|
||||
>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-subtle-on-light'
|
||||
onClick={handleAuthenticate}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Authenticate
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-ghosted-on-light'
|
||||
onClick={testAuthentication}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Retry Connection
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='3 - Add Document ID'
|
||||
completed={state.id.complet}
|
||||
disabled={!state.authenticate.complet}
|
||||
error={state.id.message}
|
||||
>
|
||||
<HStack>
|
||||
<Input
|
||||
type='text'
|
||||
size='sm'
|
||||
variant='ontime-filled-on-light'
|
||||
disabled={!state.authenticate.complet}
|
||||
value={id}
|
||||
onChange={(event) => setSheetId(event.target.value)}
|
||||
/>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' padding='0 2em' onClick={testSheetId}>
|
||||
Connect
|
||||
</Button>
|
||||
</HStack>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='4 - Select Worksheet to import'
|
||||
completed={state.worksheet.complet}
|
||||
disabled={worksheetOptions.length == 0}
|
||||
>
|
||||
<Select
|
||||
size='sm'
|
||||
disabled={worksheetOptions.length == 0}
|
||||
placeholder='Select a worksheet'
|
||||
onChange={(event) => testWorksheet(event.target.value)}
|
||||
value={worksheet}
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={handlePullData}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
</>
|
||||
{worksheetOptions.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{value}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Step>
|
||||
|
||||
<Step title='5 - Upload / Download rundown' completed={false} disabled={!state.worksheet.complet}>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={() => setDirection('up')}
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={() => setDirection('down')}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<PreviewExcel
|
||||
rundown={rundown ?? []}
|
||||
@@ -388,10 +417,41 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{rundown && (
|
||||
<Button variant='ontime-filled' padding='0 2em' onClick={handleFinalise}>
|
||||
Import
|
||||
</Button>
|
||||
{rundown ? (
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setRundown(null);
|
||||
setDirection('none');
|
||||
}}
|
||||
variant='ontime-ghost-on-light'
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
<Button variant='ontime-filled' padding='0 2em' onClick={handleFinalise}>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
) : direction === 'up' ? (
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button onClick={() => setDirection('none')} variant='ontime-ghost-on-light'>
|
||||
Go Back
|
||||
</Button>
|
||||
<Button variant='ontime-filled' padding='0 2em' onClick={handlePushData}>
|
||||
Upload
|
||||
</Button>
|
||||
</div>
|
||||
) : direction === 'down' ? (
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button onClick={() => setDirection('none')} variant='ontime-ghost-on-light'>
|
||||
Go Back
|
||||
</Button>
|
||||
<Button variant='ontime-filled' padding='0 2em' onClick={handlePullData}>
|
||||
Review
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -554,10 +554,10 @@ export const postWorksheet = async (req, res) => {
|
||||
* @description STEP-5 POST download undown to sheet
|
||||
* @returns parsed result
|
||||
*/
|
||||
export async function previewSheet(req, res) {
|
||||
export async function pullSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.body;
|
||||
const data = await sheet.pull(id, worksheet);
|
||||
const { id, options } = req.body;
|
||||
const data = await sheet.pull(id, options);
|
||||
res.status(200).send(data);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
@@ -569,8 +569,8 @@ export async function previewSheet(req, res) {
|
||||
*/
|
||||
export async function pushSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.data;
|
||||
await sheet.push(id, worksheet);
|
||||
const { id, options } = req.body;
|
||||
await sheet.push(id, options);
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
|
||||
@@ -171,3 +171,13 @@ export const validateWorksheet = [
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
export const validateSheetOptions = [
|
||||
body('id').exists().isString(),
|
||||
// body('options').exists().isObject(), TODO:
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
postHTTP,
|
||||
getAuthenticationUrl,
|
||||
uploadSheetClientFile,
|
||||
previewSheet,
|
||||
pullSheet,
|
||||
pushSheet,
|
||||
postId,
|
||||
getAuthentication,
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
validateOscSubscription,
|
||||
validateSheetid,
|
||||
validateWorksheet,
|
||||
validateSheetOptions,
|
||||
} from '../controllers/ontimeController.validate.js';
|
||||
import { projectSanitiser } from '../controllers/projectController.validate.js';
|
||||
|
||||
@@ -106,7 +107,6 @@ router.post('/http', validateHTTP, postHTTP);
|
||||
// create route between controller and '/ontime/new' endpoint
|
||||
router.post('/new', projectSanitiser, postNew);
|
||||
|
||||
|
||||
//SETP-1
|
||||
router.post('/sheet/clientsecrect', uploadFile, uploadSheetClientFile);
|
||||
router.get('/sheet/clientsecrect', uploadFile, getClientSecrect);
|
||||
@@ -122,7 +122,7 @@ router.post('/sheet/id', validateSheetid, postId);
|
||||
router.post('/sheet/worksheet', validateWorksheet, postId);
|
||||
|
||||
//STEP-5 download and generate preview
|
||||
router.post('/sheet-preview', validateWorksheet, previewSheet);
|
||||
router.post('/sheet/pull', validateSheetOptions, pullSheet);
|
||||
|
||||
//STEP-5 upload
|
||||
router.post('/sheet-push', validateWorksheet, pushSheet);
|
||||
router.post('/sheet-push', validateSheetOptions, pushSheet);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ensureDirectory } from './fileManagement.js';
|
||||
import { cellRequenstFromEvent, cellRequenstFromProjectData, getA1Notation } from './sheetUtils.js';
|
||||
import { parseExcel } from './parser.js';
|
||||
import { parseProject, parseRundown, parseUserFields } from './parserFunctions.js';
|
||||
import { ExcelImportMap } from 'ontime-utils';
|
||||
|
||||
type ResponseOK = {
|
||||
data: Partial<DatabaseModel>;
|
||||
@@ -265,11 +266,11 @@ class Sheet {
|
||||
/**
|
||||
* @description SETP 5 - Upload the rundown to sheet
|
||||
* @param {string} id - id of the sheet https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0
|
||||
* @param {string} worksheet - the name of the worksheet containing ontime data
|
||||
* @param {ExcelImportMap} options
|
||||
* @throws
|
||||
*/
|
||||
public async push(id: string, worksheet: string) {
|
||||
const { worksheetId, range } = await this.exist(id, worksheet);
|
||||
public async push(id: string, options: ExcelImportMap) {
|
||||
const { worksheetId, range } = await this.exist(id, options.worksheet);
|
||||
|
||||
const rq = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.values.get({
|
||||
spreadsheetId: id,
|
||||
@@ -278,7 +279,7 @@ class Sheet {
|
||||
range: range,
|
||||
});
|
||||
if (rq.status === 200) {
|
||||
const { rundownMetadata, projectMetadata } = parseExcel(rq.data.values);
|
||||
const { rundownMetadata, projectMetadata } = parseExcel(rq.data.values, options);
|
||||
const rundown = DataProvider.getRundown();
|
||||
const projectData = DataProvider.getProjectData();
|
||||
const titleRow = Object.values(rundownMetadata)[0]['row'];
|
||||
@@ -344,12 +345,12 @@ class Sheet {
|
||||
/**
|
||||
* @description SETP 5 - Downpload the rundown from sheet
|
||||
* @param {string} id - id of the sheet https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0
|
||||
* @param {string} worksheet - the name of the worksheet containing ontime data
|
||||
* @param {ExcelImportMap} options
|
||||
* @returns {Promise<Partial<ResponseOK>>}
|
||||
* @throws
|
||||
*/
|
||||
public async pull(id: string, worksheet: string): Promise<Partial<ResponseOK>> {
|
||||
const { range } = await this.exist(id, worksheet);
|
||||
public async pull(id: string, options: ExcelImportMap): Promise<Partial<ResponseOK>> {
|
||||
const { range } = await this.exist(id, options.worksheet);
|
||||
|
||||
const res: Partial<ResponseOK> = {};
|
||||
|
||||
@@ -362,12 +363,13 @@ class Sheet {
|
||||
|
||||
if (rq.status === 200) {
|
||||
res.data = {};
|
||||
const dataFromSheet = parseExcel(rq.data.values);
|
||||
const dataFromSheet = parseExcel(rq.data.values, options);
|
||||
res.data.rundown = parseRundown(dataFromSheet);
|
||||
if (res.data.rundown.length < 1) {
|
||||
throw new Error(`Sheet: Could not find data to import in the worksheet`);
|
||||
}
|
||||
console.log(parseProject(dataFromSheet));
|
||||
console.log(dataFromSheet.project);
|
||||
console.log(dataFromSheet.userFields);
|
||||
res.data.project = parseProject(dataFromSheet);
|
||||
res.data.userFields = parseUserFields(dataFromSheet);
|
||||
return res;
|
||||
|
||||
Reference in New Issue
Block a user