mirror of
https://github.com/cpvalente/ontime.git
synced 2026-07-26 10:38:55 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 342533e128 |
@@ -1,38 +1,45 @@
|
||||
import axios from 'axios';
|
||||
import { CustomField, CustomFieldKey, CustomFields } from 'ontime-types';
|
||||
import { CustomField, CustomFieldKey } from 'ontime-types'; // Removed CustomFields
|
||||
|
||||
import { apiEntryUrl } from './constants';
|
||||
|
||||
// Define CustomFieldWithKey for client-side usage
|
||||
export type CustomFieldWithKey = CustomField & { key: CustomFieldKey };
|
||||
|
||||
const customFieldsPath = `${apiEntryUrl}/custom-fields`;
|
||||
|
||||
/**
|
||||
* Requests list of known custom fields
|
||||
* Requests list of known custom fields, sorted by order
|
||||
*/
|
||||
export async function getCustomFields(): Promise<CustomFields> {
|
||||
const res = await axios.get(customFieldsPath);
|
||||
export async function getCustomFields(): Promise<CustomFieldWithKey[]> {
|
||||
const res = await axios.get<CustomFieldWithKey[]>(customFieldsPath);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets list of known custom fields
|
||||
* Returns the updated list, sorted by order
|
||||
*/
|
||||
export async function postCustomField(newField: CustomField): Promise<CustomFields> {
|
||||
const res = await axios.post(customFieldsPath, { ...newField });
|
||||
export async function postCustomField(newField: CustomField): Promise<CustomFieldWithKey[]> {
|
||||
const res = await axios.post<CustomFieldWithKey[]>(customFieldsPath, { ...newField });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits single custom field
|
||||
* Returns the updated list, sorted by order
|
||||
*/
|
||||
export async function editCustomField(key: CustomFieldKey, newField: CustomField): Promise<CustomFields> {
|
||||
const res = await axios.put(`${customFieldsPath}/${key}`, { ...newField });
|
||||
export async function editCustomField(key: CustomFieldKey, newField: Partial<CustomField>): Promise<CustomFieldWithKey[]> {
|
||||
// Ensure newField can include 'order' by using Partial<CustomField>
|
||||
const res = await axios.put<CustomFieldWithKey[]>(`${customFieldsPath}/${key}`, { ...newField });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes single custom field
|
||||
* Returns the updated list, sorted by order
|
||||
*/
|
||||
export async function deleteCustomField(key: CustomFieldKey): Promise<CustomFields> {
|
||||
const res = await axios.delete(`${customFieldsPath}/${key}`);
|
||||
export async function deleteCustomField(key: CustomFieldKey): Promise<CustomFieldWithKey[]> {
|
||||
const res = await axios.delete<CustomFieldWithKey[]>(`${customFieldsPath}/${key}`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { CustomFields } from 'ontime-types';
|
||||
|
||||
// CustomFields record type is no longer used here
|
||||
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||
import { CUSTOM_FIELDS } from '../api/constants';
|
||||
import { getCustomFields } from '../api/customFields';
|
||||
import { getCustomFields, CustomFieldWithKey } from '../api/customFields'; // Import CustomFieldWithKey
|
||||
|
||||
const placeholder: CustomFields = {};
|
||||
const placeholder: CustomFieldWithKey[] = []; // Placeholder is now an empty array
|
||||
|
||||
export default function useCustomFields() {
|
||||
const { data, status, isFetching, isError, refetch } = useQuery({
|
||||
// Explicitly type the useQuery hook
|
||||
const { data, status, isFetching, isError, refetch } = useQuery<CustomFieldWithKey[], Error>({
|
||||
queryKey: CUSTOM_FIELDS,
|
||||
queryFn: getCustomFields,
|
||||
placeholderData: (previousData, _previousQuery) => previousData,
|
||||
placeholderData: (previousData, _previousQuery) => previousData ?? placeholder,
|
||||
retry: 5,
|
||||
retryDelay: (attempt) => attempt * 2500,
|
||||
refetchInterval: queryRefetchIntervalSlow,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react'; // Import useEffect
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { CustomField, CustomFieldKey } from 'ontime-types';
|
||||
|
||||
import { deleteCustomField, editCustomField, postCustomField } from '../../../../common/api/customFields';
|
||||
// Import CustomFieldWithKey
|
||||
import { deleteCustomField, editCustomField, postCustomField, CustomFieldWithKey } from '../../../../common/api/customFields';
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import Info from '../../../../common/components/info/Info';
|
||||
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
|
||||
@@ -14,8 +15,61 @@ import CustomFieldEntry from './composite/CustomFieldEntry';
|
||||
import CustomFieldForm from './composite/CustomFieldForm';
|
||||
|
||||
export default function CustomFieldSettings() {
|
||||
const { data, refetch } = useCustomFields();
|
||||
const { data, refetch } = useCustomFields(); // data is CustomFieldWithKey[]
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [displayedFields, setDisplayedFields] = useState<CustomFieldWithKey[]>([]);
|
||||
const [orderChanged, setOrderChanged] = useState(false); // To track if order has changed for enabling Save button
|
||||
const [isSavingOrder, setIsSavingOrder] = useState(false); // For Save Order button loading state
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize displayedFields with fetched data, ensuring a fresh copy for local manipulation
|
||||
// And sort it initially, as the backend already provides it sorted, but this ensures consistency.
|
||||
setDisplayedFields(data ? [...data].sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) : []);
|
||||
// When data is refetched from server (e.g. after save), reset orderChanged flag
|
||||
setOrderChanged(false);
|
||||
}, [data]);
|
||||
|
||||
const handleSaveOrder = async () => {
|
||||
setIsSavingOrder(true);
|
||||
const updatePromises = [];
|
||||
|
||||
// Create a map of original orders for quick lookup
|
||||
const originalFieldsMap = new Map(data.map(f => [f.key, f]));
|
||||
|
||||
for (let i = 0; i < displayedFields.length; i++) {
|
||||
const currentField = displayedFields[i];
|
||||
const originalField = originalFieldsMap.get(currentField.key);
|
||||
|
||||
// The new order is its current index in the displayedFields array
|
||||
const newOrder = i;
|
||||
|
||||
// Check if the effective order has changed, or if it's a new field without an original order yet (though create handles initial order)
|
||||
// or if the field itself is new and not in originalFieldsMap (less likely here as it should have been created)
|
||||
if (!originalField || originalField.order !== newOrder) {
|
||||
// Only update if the order property is actually different
|
||||
// We need to ensure the object passed to editCustomField has all required CustomField props
|
||||
// and the order. The 'key' is passed as the first argument to editCustomField.
|
||||
const fieldToSave: Partial<CustomField> = {
|
||||
label: currentField.label,
|
||||
type: currentField.type,
|
||||
colour: currentField.colour,
|
||||
order: newOrder,
|
||||
};
|
||||
updatePromises.push(editCustomField(currentField.key, fieldToSave));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(updatePromises);
|
||||
setOrderChanged(false);
|
||||
refetch(); // Refetch data from the server to confirm and get fresh state
|
||||
} catch (error) {
|
||||
console.error("Error saving custom field order:", error);
|
||||
// Potentially show an error message to the user
|
||||
} finally {
|
||||
setIsSavingOrder(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInitiateCreate = () => {
|
||||
setIsAdding(true);
|
||||
@@ -50,9 +104,14 @@ export default function CustomFieldSettings() {
|
||||
<Panel.Card>
|
||||
<Panel.SubHeader>
|
||||
Custom fields
|
||||
<Button onClick={handleInitiateCreate}>
|
||||
New <IoAdd />
|
||||
</Button>
|
||||
<Panel.InlineElements>
|
||||
<Button onClick={handleSaveOrder} variant='primary' disabled={!orderChanged || isSavingOrder} loading={isSavingOrder}>
|
||||
Save Order
|
||||
</Button>
|
||||
<Button onClick={handleInitiateCreate} disabled={isSavingOrder}>
|
||||
New <IoAdd />
|
||||
</Button>
|
||||
</Panel.InlineElements>
|
||||
</Panel.SubHeader>
|
||||
<Panel.Divider />
|
||||
<Panel.Section>
|
||||
@@ -77,16 +136,67 @@ export default function CustomFieldSettings() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(data).map(([key, { colour, label, type }]) => {
|
||||
{displayedFields.map((field, index) => { // Iterate over displayedFields
|
||||
return (
|
||||
<CustomFieldEntry
|
||||
key={key}
|
||||
fieldKey={key}
|
||||
colour={colour}
|
||||
label={label}
|
||||
type={type}
|
||||
key={field.key}
|
||||
fieldKey={field.key}
|
||||
colour={field.colour}
|
||||
label={field.label}
|
||||
type={field.type}
|
||||
order={field.order} // Pass order
|
||||
onEdit={handleEditField}
|
||||
onDelete={handleDelete}
|
||||
// For reordering
|
||||
isFirst={index === 0}
|
||||
isLast={index === displayedFields.length - 1} // Use displayedFields.length
|
||||
onMove={(direction: 'up' | 'down') => {
|
||||
const newFields = [...displayedFields];
|
||||
const fieldToMove = newFields[index];
|
||||
let neighborIndex = -1;
|
||||
|
||||
if (direction === 'up' && index > 0) {
|
||||
neighborIndex = index - 1;
|
||||
} else if (direction === 'down' && index < newFields.length - 1) {
|
||||
neighborIndex = index + 1;
|
||||
}
|
||||
|
||||
if (neighborIndex !== -1) {
|
||||
const neighborField = newFields[neighborIndex];
|
||||
|
||||
// Swap order properties
|
||||
const tempOrder = fieldToMove.order;
|
||||
fieldToMove.order = neighborField.order;
|
||||
neighborField.order = tempOrder;
|
||||
|
||||
// Actual swap in the array for immediate UI feedback before potential sort
|
||||
newFields[index] = neighborField;
|
||||
newFields[neighborIndex] = fieldToMove;
|
||||
|
||||
// Sort by the new order values to ensure dense packing if orders were sparse or undefined
|
||||
// This also handles cases where initial orders might not be perfectly sequential.
|
||||
newFields.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity));
|
||||
|
||||
// If orders became non-dense or undefined, re-assign dense orders
|
||||
// This ensures that when we save, we send a clean, sequential order.
|
||||
let orderIsDirty = false;
|
||||
newFields.forEach((f, i) => {
|
||||
if(f.order !== i) {
|
||||
f.order = i;
|
||||
orderIsDirty = true;
|
||||
}
|
||||
});
|
||||
|
||||
// If we had to re-assign orders, sort again just in case (though should be sorted)
|
||||
if(orderIsDirty) {
|
||||
newFields.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity));
|
||||
}
|
||||
|
||||
|
||||
setDisplayedFields(newFields);
|
||||
setOrderChanged(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
+14
-4
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { IoPencil, IoTrash } from 'react-icons/io5';
|
||||
import { IoArrowDown, IoArrowUp, IoPencil, IoTrash } from 'react-icons/io5';
|
||||
import { CustomField, CustomFieldKey } from 'ontime-types';
|
||||
|
||||
import IconButton from '../../../../../common/components/buttons/IconButton';
|
||||
@@ -17,15 +17,19 @@ interface CustomFieldEntryProps {
|
||||
label: string;
|
||||
fieldKey: string;
|
||||
type: 'string' | 'image';
|
||||
onEdit: (key: CustomFieldKey, patch: CustomField) => Promise<void>;
|
||||
order?: number; // Add order
|
||||
onEdit: (key: CustomFieldKey, patch: Partial<CustomField>) => Promise<void>; // Patch can be partial for order updates
|
||||
onDelete: (key: CustomFieldKey) => Promise<void>;
|
||||
isFirst: boolean;
|
||||
isLast: boolean;
|
||||
onMove: (direction: 'up' | 'down') => void; // Changed from Promise<void> to void
|
||||
}
|
||||
|
||||
export default function CustomFieldEntry(props: CustomFieldEntryProps) {
|
||||
const { colour, label, fieldKey, type, onEdit, onDelete } = props;
|
||||
const { colour, label, fieldKey, type, onEdit, onDelete, isFirst, isLast, onMove } = props;
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const handleEdit = async (patch: CustomField) => {
|
||||
const handleEdit = async (patch: CustomField) => { // This patch comes from CustomFieldForm, so it's a full CustomField
|
||||
await onEdit(fieldKey, patch);
|
||||
setIsEditing(false);
|
||||
};
|
||||
@@ -61,6 +65,12 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) {
|
||||
</CopyTag>
|
||||
</td>
|
||||
<Panel.InlineElements relation='inner' as='td'>
|
||||
<IconButton variant='ghosted-white' aria-label='Move field up' onClick={() => onMove('up')} disabled={isFirst}>
|
||||
<IoArrowUp />
|
||||
</IconButton>
|
||||
<IconButton variant='ghosted-white' aria-label='Move field down' onClick={() => onMove('down')} disabled={isLast}>
|
||||
<IoArrowDown />
|
||||
</IconButton>
|
||||
<IconButton variant='ghosted-white' aria-label='Edit entry' onClick={() => setIsEditing(true)}>
|
||||
<IoPencil />
|
||||
</IconButton>
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
import { CustomField, CustomFields, ErrorResponse } from 'ontime-types';
|
||||
import { CustomField, ErrorResponse } from 'ontime-types';
|
||||
import { getErrorMessage } from 'ontime-utils';
|
||||
|
||||
import express from 'express';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import { getProjectCustomFields } from '../rundown/rundown.dao.js';
|
||||
import { getProjectCustomFields, CustomFieldWithKey } from '../rundown/rundown.dao.js';
|
||||
import { createCustomField, editCustomField, deleteCustomField } from '../rundown/rundown.service.js';
|
||||
|
||||
import { validateCustomField, validateDeleteCustomField, validateEditCustomField } from './customFields.validation.js';
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.get('/', async (_req: Request, res: Response<CustomFields>) => {
|
||||
const customFields = getProjectCustomFields();
|
||||
res.status(200).json(customFields);
|
||||
// Response type changed to CustomFieldWithKey[]
|
||||
router.get('/', async (_req: Request, res: Response<CustomFieldWithKey[]>) => {
|
||||
const customFieldsArray = getProjectCustomFields();
|
||||
res.status(200).json(customFieldsArray);
|
||||
});
|
||||
|
||||
router.post('/', validateCustomField, async (req: Request, res: Response<CustomFields | ErrorResponse>) => {
|
||||
// The POST, PUT, DELETE operations in this router return the entire updated CustomFields object.
|
||||
// This will also need to change to return the sorted array if we want consistency.
|
||||
router.post('/', validateCustomField, async (req: Request, res: Response<CustomFieldWithKey[] | ErrorResponse>) => {
|
||||
try {
|
||||
const newFields = await createCustomField(req.body as CustomField);
|
||||
res.status(201).send(newFields);
|
||||
// req.body will include label, type, colour, and optionally order
|
||||
const newFieldsArray = await createCustomField(req.body as CustomField);
|
||||
res.status(201).send(newFieldsArray);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).send({ message });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:key', validateEditCustomField, async (req: Request, res: Response<CustomFields | ErrorResponse>) => {
|
||||
router.put('/:key', validateEditCustomField, async (req: Request, res: Response<CustomFieldWithKey[] | ErrorResponse>) => {
|
||||
try {
|
||||
const currentKey = req.params.key;
|
||||
const { colour, type, label } = req.body;
|
||||
const newFields = await editCustomField(currentKey, { label, colour, type });
|
||||
res.status(200).send(newFields);
|
||||
const { colour, type, label, order } = req.body; // order is now included
|
||||
const newFieldsArray = await editCustomField(currentKey, { label, colour, type, order });
|
||||
res.status(200).send(newFieldsArray);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).send({ message });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:key', validateDeleteCustomField, async (req: Request, res: Response<CustomFields | ErrorResponse>) => {
|
||||
router.delete('/:key', validateDeleteCustomField, async (req: Request, res: Response<CustomFieldWithKey[] | ErrorResponse>) => {
|
||||
try {
|
||||
const customFields = await deleteCustomField(req.params.key);
|
||||
res.status(200).send(customFields);
|
||||
const customFieldsArray = await deleteCustomField(req.params.key);
|
||||
res.status(200).send(customFieldsArray);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(400).send({ message });
|
||||
|
||||
@@ -13,6 +13,7 @@ export const validateCustomField = [
|
||||
}),
|
||||
body('type').isIn(['string', 'image']),
|
||||
body('colour').isString().trim(),
|
||||
body('order').optional().isNumeric().toInt(),
|
||||
|
||||
requestValidationFunction,
|
||||
];
|
||||
@@ -28,6 +29,7 @@ export const validateEditCustomField = [
|
||||
}),
|
||||
body('type').isIn(['string', 'image']),
|
||||
body('colour').isString().trim(),
|
||||
body('order').optional().isNumeric().toInt(),
|
||||
|
||||
requestValidationFunction,
|
||||
];
|
||||
|
||||
@@ -82,7 +82,21 @@ let projectCustomFields: CustomFields = {};
|
||||
|
||||
export const getCurrentRundown = (): Readonly<Rundown> => cachedRundown;
|
||||
export const getRundownMetadata = (): Readonly<RundownMetadata> => rundownMetadata;
|
||||
export const getProjectCustomFields = (): Readonly<CustomFields> => projectCustomFields;
|
||||
|
||||
export type CustomFieldWithKey = CustomField & { key: CustomFieldKey };
|
||||
export const getProjectCustomFields = (): ReadonlyArray<CustomFieldWithKey> => {
|
||||
return Object.entries(projectCustomFields)
|
||||
.map(([key, value]): CustomFieldWithKey => ({ key, ...value }))
|
||||
.sort((a, b) => {
|
||||
const orderA = a.order ?? Infinity; // Fields without order go last
|
||||
const orderB = b.order ?? Infinity;
|
||||
if (orderA === orderB) {
|
||||
// Fallback sort by key if order is the same or both undefined
|
||||
return a.key.localeCompare(b.key);
|
||||
}
|
||||
return orderA - orderB;
|
||||
});
|
||||
};
|
||||
export const getEntryWithId = (entryId: EntryId): OntimeEntry | undefined => cachedRundown.entries[entryId];
|
||||
|
||||
type Transaction = {
|
||||
@@ -563,9 +577,12 @@ function customFieldAdd(customFields: CustomFields, key: CustomFieldKey, newCust
|
||||
label: newCustomField.label,
|
||||
type: newCustomField.type,
|
||||
colour: newCustomField.colour,
|
||||
order: newCustomField.order, // Add order here
|
||||
};
|
||||
|
||||
return { [key]: newCustomField };
|
||||
// The return value of this function doesn't seem to be critically used for its content,
|
||||
// but to be safe, let's include order here too.
|
||||
return { [key]: { ...newCustomField } };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { customFieldLabelToKey } from 'ontime-utils';
|
||||
import { updateRundownData } from '../../stores/runtimeState.js';
|
||||
import { runtimeService } from '../../services/runtime-service/RuntimeService.js';
|
||||
|
||||
import { createTransaction, customFieldMutation, rundownCache, rundownMutation } from './rundown.dao.js';
|
||||
import { createTransaction, customFieldMutation, rundownCache, rundownMutation, getProjectCustomFields, CustomFieldWithKey } from './rundown.dao.js';
|
||||
import type { RundownMetadata } from './rundown.types.js';
|
||||
import { generateEvent, getInsertAfterId, hasChanges } from './rundown.utils.js';
|
||||
import { sendRefetch } from '../../adapters/WebsocketAdapter.js';
|
||||
@@ -412,7 +412,7 @@ export async function ungroupEntries(blockId: EntryId): Promise<Rundown> {
|
||||
* Adds a new custom field to the project
|
||||
* @throws if the label is missing or invalid
|
||||
*/
|
||||
export async function createCustomField(customField: CustomField): Promise<CustomFields> {
|
||||
export async function createCustomField(customField: CustomField): Promise<ReadonlyArray<CustomFieldWithKey>> {
|
||||
const key = customFieldLabelToKey(customField.label);
|
||||
|
||||
if (!key) {
|
||||
@@ -426,16 +426,27 @@ export async function createCustomField(customField: CustomField): Promise<Custo
|
||||
throw new Error('Label already exists');
|
||||
}
|
||||
|
||||
// Assign order if not provided
|
||||
if (customField.order === undefined) {
|
||||
let maxOrder = -1;
|
||||
Object.values(customFields).forEach(cf => {
|
||||
if (cf.order !== undefined && cf.order > maxOrder) {
|
||||
maxOrder = cf.order;
|
||||
}
|
||||
});
|
||||
customField.order = maxOrder + 1;
|
||||
}
|
||||
|
||||
customFieldMutation.add(customFields, key, customField);
|
||||
|
||||
// Adding a custom field has no immediate implications on the rundown
|
||||
const { customFields: resultCustomFields } = commit(false);
|
||||
commit(false); // Persist changes
|
||||
|
||||
setImmediate(() => {
|
||||
sendRefetch(RefetchKey.CustomFields);
|
||||
});
|
||||
|
||||
return resultCustomFields;
|
||||
return getProjectCustomFields(); // Return sorted array
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,7 +457,7 @@ export async function createCustomField(customField: CustomField): Promise<Custo
|
||||
* @throws if the label is missing or invalid
|
||||
* @throws if the new label already exists
|
||||
*/
|
||||
export async function editCustomField(key: CustomFieldKey, newField: Partial<CustomField>): Promise<CustomFields> {
|
||||
export async function editCustomField(key: CustomFieldKey, newField: Partial<CustomField>): Promise<ReadonlyArray<CustomFieldWithKey>> {
|
||||
const { customFields, customFieldsMetadata, rundown, commit } = createTransaction({
|
||||
mutableRundown: true,
|
||||
mutableCustomFields: true,
|
||||
@@ -470,26 +481,27 @@ export async function editCustomField(key: CustomFieldKey, newField: Partial<Cus
|
||||
}
|
||||
|
||||
// the custom fields have been removed and there is no processing to be done
|
||||
const { rundownMetadata, revision, customFields: resultCustomFields } = commit(false);
|
||||
const { rundownMetadata, revision } = commit(false); // Persist changes
|
||||
|
||||
// schedule the side effects
|
||||
setImmediate(() => {
|
||||
notifyChanges(rundownMetadata, revision, { timer: true, external: true });
|
||||
sendRefetch(RefetchKey.CustomFields); // Also refetch custom fields explicitly
|
||||
});
|
||||
|
||||
return resultCustomFields;
|
||||
return getProjectCustomFields(); // Return sorted array
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an existing custom field
|
||||
*/
|
||||
export async function deleteCustomField(key: CustomFieldKey): Promise<CustomFields> {
|
||||
export async function deleteCustomField(key: CustomFieldKey): Promise<ReadonlyArray<CustomFieldWithKey>> {
|
||||
const { customFields, customFieldsMetadata, rundown, commit } = createTransaction({
|
||||
mutableRundown: true,
|
||||
mutableCustomFields: true,
|
||||
});
|
||||
if (!(key in customFields)) {
|
||||
return customFields;
|
||||
return getProjectCustomFields(); // Return sorted array even if key not found
|
||||
}
|
||||
|
||||
customFieldMutation.remove(customFields, key);
|
||||
@@ -498,14 +510,15 @@ export async function deleteCustomField(key: CustomFieldKey): Promise<CustomFiel
|
||||
}
|
||||
|
||||
// the custom fields have been removed and there is no processing to be done
|
||||
const { rundownMetadata, revision, customFields: resultCustomFields } = commit(false);
|
||||
const { rundownMetadata, revision } = commit(false); // Persist changes
|
||||
|
||||
// schedule the side effects
|
||||
setImmediate(() => {
|
||||
notifyChanges(rundownMetadata, revision, { timer: true, external: true });
|
||||
sendRefetch(RefetchKey.CustomFields); // Also refetch custom fields explicitly
|
||||
});
|
||||
|
||||
return resultCustomFields;
|
||||
return getProjectCustomFields(); // Return sorted array
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ export type CustomField = {
|
||||
type: 'string' | 'image';
|
||||
colour: string;
|
||||
label: string;
|
||||
order?: number;
|
||||
};
|
||||
|
||||
export type CustomFields = Record<CustomFieldKey, CustomField>;
|
||||
|
||||
Reference in New Issue
Block a user