mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 11:53:49 +00:00
76b4ae8b39
The settings UI had accumulated three inconsistencies. This addresses all three without renaming any group or breaking `?settings=` deep links. Empty states - Add `Panel.EmptyState` / `Panel.TableEmpty` (title + description + action), promoting the treatment previously local to the spreadsheet PreviewTable. - Add missing empty states to Custom fields, Projects, Rundowns and both Client tables, which previously rendered bare column headers. - Convert the remaining `TableEmpty` call sites off the generic "No data yet". - Rename `.empty` to `.loaderBox` in ProjectPanel, where it meant a loader box rather than an empty state. Entity forms in modals - Add `useEntityModal`, replacing six different open/close mechanisms. - Move the multi-field create/edit forms into `Modal`: automations (wide), triggers, URL presets, custom fields, custom views, new rundown and project merge. Single-field row renames stay inline. - Create and edit now share one surface instead of rendering above the table and in-place respectively, and the list is no longer interactive underneath. - Remove the duplicate project create form; the "New" button now routes to the existing QuickStart modal. Sidebar - Add a search box with per-section keywords, so sections are reachable by the term users have in mind (osc, alias, google sheet, pin...). - Nav items are real buttons in a nav landmark with aria-current; secondary items were previously unreachable by keyboard. - Fix duplicated `manage__sheets` id, which highlighted two entries at once. Also - Re-scroll to a section when its nav item is selected again, and align to the top rather than the centre. - Close button no longer floats over content; drop the 300px padding hack. - Tables scroll with the panel instead of owning a nested scroll area. - Derive divider/table/list padding from a card padding custom property. - Fix an unreachable branch that stopped duplicate custom field labels from being rejected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UP4knuna8kLe79NAuNDhUJ
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
import { useQueryClient } from '@tanstack/react-query';
|
|
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
|
|
import { PROJECT_DATA } from '../../../../common/api/constants';
|
|
import { getDb, patchData } from '../../../../common/api/db';
|
|
import { maybeAxiosError } from '../../../../common/api/utils';
|
|
import Button from '../../../../common/components/buttons/Button';
|
|
import Info from '../../../../common/components/info/Info';
|
|
import Modal from '../../../../common/components/modal/Modal';
|
|
import Switch from '../../../../common/components/switch/Switch';
|
|
import { cx } from '../../../../common/utils/styleUtils';
|
|
import * as Panel from '../../panel-utils/PanelUtils';
|
|
import { makeProjectPatch } from './project.utils';
|
|
|
|
import style from './ProjectPanel.module.scss';
|
|
|
|
const formId = 'project-merge-form';
|
|
|
|
interface ProjectMergeFromProps {
|
|
onClose: () => void;
|
|
fileName: string;
|
|
}
|
|
|
|
type ProjectMergeFormValues = {
|
|
project: boolean;
|
|
rundowns: boolean;
|
|
viewSettings: boolean;
|
|
urlPresets: boolean;
|
|
automation: boolean;
|
|
};
|
|
|
|
export default function ProjectMergeForm({ onClose, fileName }: ProjectMergeFromProps) {
|
|
const [error, setError] = useState<string | null>(null);
|
|
const queryClient = useQueryClient();
|
|
|
|
const {
|
|
handleSubmit,
|
|
watch,
|
|
setValue,
|
|
formState: { isSubmitting, isValid, isDirty },
|
|
} = useForm<ProjectMergeFormValues>({
|
|
defaultValues: {
|
|
project: false,
|
|
rundowns: false,
|
|
viewSettings: false,
|
|
urlPresets: false,
|
|
automation: false,
|
|
},
|
|
resetOptions: {
|
|
keepDirtyValues: true,
|
|
},
|
|
});
|
|
|
|
const handleSubmitCreate = async (values: ProjectMergeFormValues) => {
|
|
const allFalse = Object.values(values).every((value) => !value);
|
|
if (allFalse) {
|
|
setError('At least one option must be selected');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setError(null);
|
|
|
|
// make patch object
|
|
const { data } = await getDb(fileName);
|
|
if (!data.settings.version.startsWith('4.')) {
|
|
setError('The project you are attempting to merge is from an older version and it need to be migrated first');
|
|
return;
|
|
}
|
|
const patch = await makeProjectPatch(data, values);
|
|
|
|
// request patch
|
|
await patchData(patch);
|
|
await queryClient.invalidateQueries({ queryKey: PROJECT_DATA });
|
|
onClose();
|
|
} catch (error) {
|
|
setError(maybeAxiosError(error));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
isOpen
|
|
onClose={onClose}
|
|
showBackdrop
|
|
showCloseButton
|
|
title='Partial project merge'
|
|
footerElements={
|
|
<>
|
|
{error && <Panel.Error>{error}</Panel.Error>}
|
|
<Button onClick={onClose} variant='ghosted' disabled={isSubmitting}>
|
|
Cancel
|
|
</Button>
|
|
<Button type='submit' form={formId} disabled={!isValid || !isDirty} loading={isSubmitting} variant='primary'>
|
|
Merge
|
|
</Button>
|
|
</>
|
|
}
|
|
bodyElements={
|
|
<form id={formId} onSubmit={handleSubmit(handleSubmitCreate)}>
|
|
<Panel.Section className={cx([style.innerColumn, style.inlineLabels])}>
|
|
<Panel.Description>
|
|
Select data from <i>{`"${fileName}"`}</i> to merge into the current project.
|
|
</Panel.Description>
|
|
<Info type='warning'>
|
|
This process is irreversible and can result in data loss. <br />
|
|
You may want to create a duplicate backup beforehand.
|
|
</Info>
|
|
<label>
|
|
<Switch
|
|
size='large'
|
|
checked={watch('project')}
|
|
onCheckedChange={(value: boolean) => setValue('project', value, { shouldDirty: true })}
|
|
/>
|
|
Project data
|
|
</label>
|
|
<label>
|
|
<Switch
|
|
size='large'
|
|
checked={watch('rundowns')}
|
|
onCheckedChange={(value: boolean) => setValue('rundowns', value, { shouldDirty: true })}
|
|
/>
|
|
Rundown + Custom Fields
|
|
</label>
|
|
<label>
|
|
<Switch
|
|
size='large'
|
|
checked={watch('viewSettings')}
|
|
onCheckedChange={(value: boolean) => setValue('viewSettings', value, { shouldDirty: true })}
|
|
/>
|
|
View Settings
|
|
</label>
|
|
<label>
|
|
<Switch
|
|
size='large'
|
|
checked={watch('urlPresets')}
|
|
onCheckedChange={(value: boolean) => setValue('urlPresets', value, { shouldDirty: true })}
|
|
/>
|
|
URL Presets
|
|
</label>
|
|
<label>
|
|
<Switch
|
|
size='large'
|
|
checked={watch('automation')}
|
|
onCheckedChange={(value: boolean) => setValue('automation', value, { shouldDirty: true })}
|
|
/>
|
|
Automation Settings
|
|
</label>
|
|
</Panel.Section>
|
|
</form>
|
|
}
|
|
/>
|
|
);
|
|
}
|