mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 01:43:43 +00:00
refactor: add rundown summary to import preview
This commit is contained in:
committed by
Carlos Valente
parent
3918664945
commit
e6070e6efd
@@ -1,5 +1,5 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { CustomFields, Rundown } from 'ontime-types';
|
||||
import { CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
@@ -25,6 +25,7 @@ export async function upload(file: File): Promise<string[]> {
|
||||
type PreviewSpreadsheetResponse = {
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
};
|
||||
export async function importRundownPreview(options: ImportMap): Promise<PreviewSpreadsheetResponse> {
|
||||
const response: AxiosResponse<PreviewSpreadsheetResponse> = await axios.post(`${excelPath}/preview`, {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { AuthenticationStatus, CustomFields, Rundown } from 'ontime-types';
|
||||
import { AuthenticationStatus, CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
@@ -56,6 +56,7 @@ export const previewRundown = async (
|
||||
): Promise<{
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
}> => {
|
||||
const response = await axios.post(`${sheetsPath}/${sheetId}/read`, { options });
|
||||
return response.data;
|
||||
|
||||
+21
-16
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { CustomFields, Rundown } from 'ontime-types';
|
||||
import { getFirstEventNormal, getLastEventNormal, millisToString } from 'ontime-utils';
|
||||
import { CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import Button from '../../../../../common/components/buttons/Button';
|
||||
import useRundown from '../../../../../common/hooks-query/useRundown';
|
||||
import { formatDuration } from '../../../../../common/utils/time';
|
||||
import * as Panel from '../../../panel-utils/PanelUtils';
|
||||
|
||||
import PreviewSpreadsheet from './preview/PreviewRundown';
|
||||
@@ -13,12 +14,20 @@ import { useSheetStore } from './useSheetStore';
|
||||
interface ImportReviewProps {
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
onFinished: () => void;
|
||||
onCancel: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
export default function ImportReview({ rundown, customFields, onFinished, onCancel, onBack }: ImportReviewProps) {
|
||||
export default function ImportReview({
|
||||
rundown,
|
||||
customFields,
|
||||
summary,
|
||||
onFinished,
|
||||
onCancel,
|
||||
onBack,
|
||||
}: ImportReviewProps) {
|
||||
const { data: currentRundown } = useRundown();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { importRundown } = useGoogleSheet();
|
||||
@@ -44,9 +53,6 @@ export default function ImportReview({ rundown, customFields, onFinished, onCanc
|
||||
onFinished();
|
||||
};
|
||||
|
||||
const { firstEvent } = getFirstEventNormal(rundown.entries, rundown.flatOrder);
|
||||
const { lastEvent } = getLastEventNormal(rundown.entries, rundown.flatOrder);
|
||||
|
||||
return (
|
||||
<Panel.Section>
|
||||
<Panel.Title>
|
||||
@@ -70,16 +76,15 @@ export default function ImportReview({ rundown, customFields, onFinished, onCanc
|
||||
<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.ListItem>
|
||||
<b>Start time</b> {millisToString(summary.start)}
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<b>End time</b> {millisToString(summary.end)}
|
||||
</Panel.ListItem>
|
||||
<Panel.ListItem>
|
||||
<b>Total duration</b> {formatDuration(summary.duration)}
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
<PreviewSpreadsheet rundown={rundown} customFields={customFields} />
|
||||
</Panel.Section>
|
||||
|
||||
+6
-1
@@ -36,6 +36,8 @@ export default function SourcesPanel() {
|
||||
const setRundown = useSheetStore((state) => state.setRundown);
|
||||
const customFields = useSheetStore((state) => state.customFields);
|
||||
const setCustomFields = useSheetStore((state) => state.setCustomFields);
|
||||
const summary = useSheetStore((state) => state.summary);
|
||||
const setSummary = useSheetStore((state) => state.setSummary);
|
||||
const setSheetId = useSheetStore((state) => state.setSheetId);
|
||||
const sheetId = useSheetStore((state) => state.sheetId);
|
||||
const resetPreview = useSheetStore((state) => state.resetPreview);
|
||||
@@ -76,6 +78,7 @@ export default function SourcesPanel() {
|
||||
setHasFile('none');
|
||||
setWorksheets(null);
|
||||
setCustomFields(null);
|
||||
setSummary(null);
|
||||
setError('');
|
||||
setSheetId(null);
|
||||
};
|
||||
@@ -109,6 +112,7 @@ export default function SourcesPanel() {
|
||||
const previewData = await importRundownPreviewExcel(importMap);
|
||||
setRundown(previewData.rundown);
|
||||
setCustomFields(previewData.customFields);
|
||||
setSummary(previewData.summary);
|
||||
} catch (error) {
|
||||
setError(maybeAxiosError(error));
|
||||
}
|
||||
@@ -151,7 +155,7 @@ export default function SourcesPanel() {
|
||||
const showCompleted = importFlow === 'finished';
|
||||
const showAuth = isGSheetFlow && !isAuthenticated;
|
||||
const showImportMap = (isGSheetFlow && isAuthenticated) || (isExcelFlow && hasFile === 'done');
|
||||
const showReview = rundown !== null && customFields !== null;
|
||||
const showReview = rundown !== null && customFields !== null && summary !== null;
|
||||
|
||||
return (
|
||||
<Panel.Section>
|
||||
@@ -218,6 +222,7 @@ export default function SourcesPanel() {
|
||||
<ImportReview
|
||||
rundown={rundown}
|
||||
customFields={customFields}
|
||||
summary={summary}
|
||||
onFinished={handleFinished}
|
||||
onCancel={cancelImportMap}
|
||||
onBack={resetPreview}
|
||||
|
||||
+2
@@ -21,6 +21,7 @@ export default function useGoogleSheet() {
|
||||
const patchStepData = useSheetStore((state) => state.patchStepData);
|
||||
const setRundown = useSheetStore((state) => state.setRundown);
|
||||
const setCustomFields = useSheetStore((state) => state.setCustomFields);
|
||||
const setSummary = useSheetStore((state) => state.setSummary);
|
||||
|
||||
/** whether the current session has been authenticated */
|
||||
const verifyAuth = async (): Promise<{ authenticated: AuthenticationStatus; sheetId: string } | void> => {
|
||||
@@ -58,6 +59,7 @@ export default function useGoogleSheet() {
|
||||
const data = await previewRundown(sheetId, fileOptions);
|
||||
setRundown(data.rundown);
|
||||
setCustomFields(data.customFields);
|
||||
setSummary(data.summary);
|
||||
} catch (error) {
|
||||
patchStepData({ pullPush: { available: true, error: maybeAxiosError(error) } });
|
||||
}
|
||||
|
||||
+7
-4
@@ -1,4 +1,4 @@
|
||||
import { AuthenticationStatus, CustomFields, Rundown } from 'ontime-types';
|
||||
import { AuthenticationStatus, CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { defaultImportMap, ImportMap } from 'ontime-utils';
|
||||
import { create } from 'zustand';
|
||||
|
||||
@@ -17,10 +17,10 @@ type SheetStore = {
|
||||
// we get this from a preview response
|
||||
rundown: Rundown | null;
|
||||
setRundown: (rundown: Rundown | null) => void;
|
||||
|
||||
// we get this from a preview response
|
||||
customFields: CustomFields | null;
|
||||
setCustomFields: (customFields: CustomFields | null) => void;
|
||||
summary: RundownSummary | null;
|
||||
setSummary: (metadata: RundownSummary | null) => void;
|
||||
|
||||
spreadsheetImportMap: ImportMap;
|
||||
patchSpreadsheetImportMap: <T extends keyof ImportMap>(field: T, value: ImportMap[T]) => void;
|
||||
@@ -43,6 +43,7 @@ const initialState = {
|
||||
authenticationStatus: 'not_authenticated' as AuthenticationStatus,
|
||||
rundown: null,
|
||||
customFields: null,
|
||||
summary: null,
|
||||
spreadsheetImportMap: defaultImportMap,
|
||||
};
|
||||
|
||||
@@ -64,6 +65,8 @@ export const useSheetStore = create<SheetStore>((set, get) => ({
|
||||
|
||||
setCustomFields: (customFields: CustomFields | null) => set({ customFields }),
|
||||
|
||||
setSummary: (summary: RundownSummary | null) => set({ summary }),
|
||||
|
||||
patchSpreadsheetImportMap: <T extends keyof ImportMap>(field: T, value: ImportMap[T]) => {
|
||||
const currentImportMap = get().spreadsheetImportMap;
|
||||
if (currentImportMap[field] !== value) {
|
||||
@@ -72,5 +75,5 @@ export const useSheetStore = create<SheetStore>((set, get) => ({
|
||||
},
|
||||
|
||||
reset: () => set(initialState),
|
||||
resetPreview: () => set({ rundown: null, customFields: null }),
|
||||
resetPreview: () => set({ rundown: null, customFields: null, summary: null }),
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from 'express';
|
||||
import type { Request, Response } from 'express';
|
||||
import { CustomFields, ErrorResponse, Rundown } from 'ontime-types';
|
||||
import { CustomFields, ErrorResponse, Rundown, RundownSummary } from 'ontime-types';
|
||||
|
||||
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||
|
||||
@@ -32,7 +32,10 @@ router.post(
|
||||
router.post(
|
||||
'/preview',
|
||||
validateImportMapOptions,
|
||||
(req: Request, res: Response<{ rundown: Rundown; customFields: CustomFields } | ErrorResponse>) => {
|
||||
(
|
||||
req: Request,
|
||||
res: Response<{ rundown: Rundown; customFields: CustomFields; summary: RundownSummary } | ErrorResponse>,
|
||||
) => {
|
||||
try {
|
||||
const { options } = req.body;
|
||||
const data = generateRundownPreview(options);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Google Sheets
|
||||
*/
|
||||
|
||||
import { CustomFields, Rundown } from 'ontime-types';
|
||||
import { CustomFields, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { type ImportMap } from 'ontime-utils';
|
||||
|
||||
import { extname } from 'path';
|
||||
@@ -14,7 +14,7 @@ import type { WorkBook } from 'xlsx';
|
||||
import { deleteFile } from '../../utils/fileManagement.js';
|
||||
|
||||
import { parseRundown } from '../rundown/rundown.parser.js';
|
||||
import { getProjectCustomFields } from '../rundown/rundown.dao.js';
|
||||
import { getProjectCustomFields, processRundown } from '../rundown/rundown.dao.js';
|
||||
import { parseCustomFields } from '../custom-fields/customFields.parser.js';
|
||||
|
||||
import { parseExcel } from './excel.parser.js';
|
||||
@@ -43,7 +43,11 @@ export async function readExcelFile(filePath: string): Promise<string[]> {
|
||||
return excelData.SheetNames;
|
||||
}
|
||||
|
||||
export function generateRundownPreview(options: ImportMap): { rundown: Rundown; customFields: CustomFields } {
|
||||
export function generateRundownPreview(options: ImportMap): {
|
||||
rundown: Rundown;
|
||||
summary: RundownSummary;
|
||||
customFields: CustomFields;
|
||||
} {
|
||||
const data = excelData.Sheets[options.worksheet];
|
||||
|
||||
if (!data) {
|
||||
@@ -59,9 +63,25 @@ export function generateRundownPreview(options: ImportMap): { rundown: Rundown;
|
||||
|
||||
// we run the parsed data through an extra step to ensure the objects shape
|
||||
const customFields = parseCustomFields(dataFromExcel);
|
||||
const rundown = parseRundown(dataFromExcel.rundown, customFields);
|
||||
const parsedRundown = parseRundown(dataFromExcel.rundown, customFields);
|
||||
const processedRundown = processRundown(parsedRundown, customFields);
|
||||
|
||||
return { rundown, customFields };
|
||||
return {
|
||||
rundown: {
|
||||
id: parsedRundown.id,
|
||||
title: parsedRundown.title,
|
||||
order: processedRundown.order,
|
||||
flatOrder: processedRundown.flatEntryOrder,
|
||||
entries: processedRundown.entries,
|
||||
revision: 0,
|
||||
},
|
||||
summary: {
|
||||
duration: processedRundown.totalDuration,
|
||||
start: processedRundown.firstStart,
|
||||
end: processedRundown.lastEnd,
|
||||
},
|
||||
customFields,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Google Sheets
|
||||
*/
|
||||
|
||||
import type { AuthenticationStatus, CustomFields, ErrorResponse, Rundown } from 'ontime-types';
|
||||
import type { AuthenticationStatus, CustomFields, ErrorResponse, Rundown, RundownSummary } from 'ontime-types';
|
||||
import { getErrorMessage } from 'ontime-utils';
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
@@ -86,6 +86,7 @@ export async function readFromSheet(
|
||||
| {
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
}
|
||||
| ErrorResponse
|
||||
>,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
MaybeString,
|
||||
OntimeGroup,
|
||||
Rundown,
|
||||
RundownSummary,
|
||||
SupportedEntry,
|
||||
} from 'ontime-types';
|
||||
import { ImportMap, getErrorMessage } from 'ontime-utils';
|
||||
@@ -24,7 +25,7 @@ import { Credentials, OAuth2Client } from 'google-auth-library';
|
||||
|
||||
import { logger } from '../../classes/Logger.js';
|
||||
import { parseRundowns } from '../../api-data/rundown/rundown.parser.js';
|
||||
import { getCurrentRundown, getProjectCustomFields } from '../../api-data/rundown/rundown.dao.js';
|
||||
import { getCurrentRundown, getProjectCustomFields, processRundown } from '../../api-data/rundown/rundown.dao.js';
|
||||
import { parseExcel } from '../../api-data/excel/excel.parser.js';
|
||||
import { parseCustomFields } from '../../api-data/custom-fields/customFields.parser.js';
|
||||
import { consoleSubdued } from '../../utils/console.js';
|
||||
@@ -455,6 +456,7 @@ export async function download(
|
||||
): Promise<{
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
}> {
|
||||
if (!currentAuthClient) {
|
||||
throw new Error('Not authenticated');
|
||||
@@ -488,9 +490,9 @@ export async function download(
|
||||
};
|
||||
|
||||
const customFields = parseCustomFields(dataModel);
|
||||
const rundowns = parseRundowns(dataModel, customFields);
|
||||
const parsedRundown = parseRundowns(dataModel, customFields);
|
||||
|
||||
const importedRundown = rundowns[rundownId];
|
||||
const importedRundown = parsedRundown[rundownId];
|
||||
if (!importedRundown) {
|
||||
throw new Error(`Sheet: Rundown with ID ${rundownId} not found in the worksheet`);
|
||||
}
|
||||
@@ -499,5 +501,22 @@ export async function download(
|
||||
throw new Error('Sheet: Could not find data to import in the worksheet');
|
||||
}
|
||||
|
||||
return { rundown: rundowns[rundownId], customFields };
|
||||
const processedRundown = processRundown(importedRundown, customFields);
|
||||
|
||||
return {
|
||||
rundown: {
|
||||
id: importedRundown.id,
|
||||
title: importedRundown.title,
|
||||
order: processedRundown.order,
|
||||
flatOrder: processedRundown.flatEntryOrder,
|
||||
entries: processedRundown.entries,
|
||||
revision: 0,
|
||||
},
|
||||
summary: {
|
||||
duration: processedRundown.totalDuration,
|
||||
start: processedRundown.firstStart,
|
||||
end: processedRundown.lastEnd,
|
||||
},
|
||||
customFields,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { EntryId, OntimeEntry } from '../../definitions/core/OntimeEntry.js';
|
||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||
|
||||
export type PatchWithId<T extends OntimeEntry = OntimeEntry> = Partial<T> & { id: EntryId };
|
||||
|
||||
@@ -23,3 +24,9 @@ export type ProjectRundownsList = {
|
||||
loaded: string;
|
||||
rundowns: ProjectRundown[];
|
||||
};
|
||||
|
||||
export type RundownSummary = {
|
||||
duration: number;
|
||||
start: MaybeNumber;
|
||||
end: MaybeNumber;
|
||||
};
|
||||
|
||||
@@ -83,6 +83,7 @@ export type {
|
||||
ProjectRundown,
|
||||
ProjectRundownsList,
|
||||
TransientEventPayload,
|
||||
RundownSummary,
|
||||
} from './api/rundown-controller/BackendResponse.type.js';
|
||||
export type { LinkOptions } from './api/session-controller/BackendResponse.type.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user