refactor: improve sheet import UX

This commit is contained in:
Carlos Valente
2026-03-23 19:26:03 +01:00
committed by Carlos Valente
parent 42dcf35f45
commit f53cb687bf
39 changed files with 1739 additions and 910 deletions
+5 -1
View File
@@ -1,5 +1,9 @@
import axios, { AxiosResponse } from 'axios';
import type { SpreadsheetPreviewResponse, SpreadsheetWorksheetMetadata, SpreadsheetWorksheetOptions } from 'ontime-types';
import type {
SpreadsheetPreviewResponse,
SpreadsheetWorksheetMetadata,
SpreadsheetWorksheetOptions,
} from 'ontime-types';
import { ImportMap } from 'ontime-utils';
import { apiEntryUrl } from './constants';
+8 -2
View File
@@ -76,6 +76,9 @@ export const previewRundown = async (
return response.data;
};
/**
* Fetches derived metadata for a single worksheet by reading its rows and detecting headers.
*/
export const getWorksheetMetadata = async (
sheetId: string,
worksheet: string,
@@ -92,12 +95,15 @@ export const getWorksheetMetadata = async (
return response.data;
};
export const getWorksheetNames = async (
/**
* Fetches the available worksheets for a Google Sheet. Metadata is loaded lazily per worksheet.
*/
export const getWorksheetOptions = async (
sheetId: string,
requestOptions?: RequestOptions,
): Promise<SpreadsheetWorksheetOptions> => {
const response: AxiosResponse<SpreadsheetWorksheetOptions> = await axios.post(
`${sheetsPath}/${sheetId}/worksheets`,
`${sheetsPath}/${sheetId}/worksheet-options`,
undefined,
{
signal: requestOptions?.signal,
@@ -1,5 +1,5 @@
import { Autocomplete as BaseAutocomplete } from '@base-ui/react/autocomplete';
import type { ReactNode } from 'react';
import type { ReactNode, Ref } from 'react';
import { useEffect, useRef, useState } from 'react';
import { cx } from '../../utils/styleUtils';
@@ -15,6 +15,7 @@ export interface AutocompleteInputProps extends Omit<InputProps, 'value' | 'defa
onValueChange: (value: string) => void;
emptyLabel?: string;
trailingElement?: (option: string) => ReactNode;
inputRef?: Ref<HTMLInputElement>;
}
export default function AutocompleteInput({
@@ -23,6 +24,7 @@ export default function AutocompleteInput({
emptyLabel,
fluid,
height = 'medium',
inputRef,
onValueChange,
options,
trailingElement,
@@ -30,16 +32,31 @@ export default function AutocompleteInput({
variant = 'subtle',
...inputProps
}: AutocompleteInputProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
const internalInputRef = useRef<HTMLInputElement | null>(null);
const [open, setOpen] = useState(false);
const handleInputRef = (node: HTMLInputElement | null) => {
internalInputRef.current = node;
if (!inputRef) {
return;
}
if (typeof inputRef === 'function') {
inputRef(node);
return;
}
inputRef.current = node;
};
// close the popover when the parent scrollable container scrolls or the window resizes
useEffect(() => {
if (!open) {
return;
}
const scrollTarget = getScrollParent(inputRef.current);
const scrollTarget = getScrollParent(internalInputRef.current);
const handleScroll = () => setOpen(false);
scrollTarget.addEventListener('scroll', handleScroll, { passive: true });
@@ -63,7 +80,7 @@ export default function AutocompleteInput({
onValueChange={onValueChange}
>
<BaseAutocomplete.Input
ref={inputRef}
ref={handleInputRef}
className={cx([
inputStyles.input,
inputStyles[variant],
@@ -30,7 +30,7 @@
.title {
color: $ui-white;
font-size: 1.125rem;
font-size: 1rem;
font-weight: 600;
}
@@ -40,8 +40,6 @@
}
.title {
font-size: 1rem;
font-weight: 600;
padding-block: 1rem;
display: flex;
@@ -49,6 +47,11 @@
justify-content: space-between;
}
.titleText {
font-size: 1rem;
font-weight: 600;
}
.body {
display: flex;
flex-direction: column;
@@ -38,9 +38,9 @@ export default function Modal({
>
<BaseDialog.Portal>
{showBackdrop && <BaseDialog.Backdrop className={style.backdrop} />}
<BaseDialog.Popup className={cx([style.modal, size === 'wide' && style.wide])}>
<BaseDialog.Popup aria-label={title} className={cx([style.modal, size === 'wide' && style.wide])}>
<div className={style.title}>
{title}
{title ? <BaseDialog.Title className={style.titleText}>{title}</BaseDialog.Title> : <div />}
{showCloseButton && (
<IconButton variant='subtle-white' onClick={onClose}>
<IoClose />