mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 06:04:05 +00:00
Feat/table (#105)
* feat/table add react-table * feat/table add protected table route * feat/table upgrade dependencies * feat/table support parsing of new properties Co-authored-by: DeepSource Bot <bot@deepsource.io>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { lazy, useEffect } from 'react';
|
||||
import React, { lazy, useEffect } from 'react';
|
||||
import { Box } from '@chakra-ui/layout';
|
||||
import { useDisclosure } from '@chakra-ui/hooks';
|
||||
import styles from './Editor.module.scss';
|
||||
import MenuBar from 'features/menu/MenuBar';
|
||||
import ModalManager from 'features/modals/ModalManager';
|
||||
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
|
||||
import { LoggingProvider } from '../../app/context/LoggingContext';
|
||||
import { LocalEventSettingsProvider } from '../../app/context/LocalEventSettingsContext';
|
||||
import { CursorProvider } from '../../app/context/CursorContext';
|
||||
import { CollapseProvider } from '../../app/context/CollapseContext';
|
||||
import styles from './Editor.module.scss';
|
||||
|
||||
const EventListWrapper = lazy(() => import('features/editors/list/EventListWrapper'));
|
||||
const PlaybackControl = lazy(() => import('features/control/PlaybackControl'));
|
||||
@@ -31,20 +32,22 @@ export default function Editor() {
|
||||
|
||||
<div className={styles.mainContainer}>
|
||||
<CursorProvider>
|
||||
<Box id='settings' className={styles.settings}>
|
||||
<ErrorBoundary>
|
||||
<MenuBar onOpen={onOpen} isOpen={isOpen} />
|
||||
</ErrorBoundary>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.editor}>
|
||||
<h1>Event List</h1>
|
||||
<div className={styles.content}>
|
||||
<CollapseProvider>
|
||||
<Box id='settings' className={styles.settings}>
|
||||
<ErrorBoundary>
|
||||
<EventListWrapper />
|
||||
<MenuBar onOpen={onOpen} isOpen={isOpen} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.editor}>
|
||||
<h1>Event List</h1>
|
||||
<div className={styles.content}>
|
||||
<ErrorBoundary>
|
||||
<EventListWrapper />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Box>
|
||||
</CollapseProvider>
|
||||
</CursorProvider>
|
||||
|
||||
<Box className={styles.messages}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Tooltip } from '@chakra-ui/tooltip';
|
||||
import { Checkbox } from '@chakra-ui/react';
|
||||
import style from './EntryBlock.module.scss';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { LocalEventSettingsContext } from '../../../app/context/LocalEventSettingsContext';
|
||||
|
||||
export default function EntryBlock(props) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import Icon from '@chakra-ui/icon';
|
||||
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import { useMemo } from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
||||
@@ -10,22 +10,15 @@ import ActionButtons from '../list/ActionButtons';
|
||||
import PublicIconBtn from 'common/components/buttons/PublicIconBtn';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
import style from './EventBlock.module.css';
|
||||
import { HandleCollapse, SelectCollapse } from 'app/context/collapseAtom';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CollapseContext } from '../../../app/context/CollapseContext';
|
||||
import style from './EventBlock.module.css';
|
||||
|
||||
const ExpandedBlock = (props) => {
|
||||
const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
const oscid = data?.id || '...';
|
||||
|
||||
// if end is before, assume is the day after
|
||||
const duration =
|
||||
data.timeStart > data.timeEnd
|
||||
? data.timeEnd + 86400000 - data.timeStart
|
||||
: data.timeEnd - data.timeStart;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
@@ -41,7 +34,7 @@ const ExpandedBlock = (props) => {
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
duration={duration}
|
||||
duration={data.duration}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
@@ -95,7 +88,7 @@ ExpandedBlock.propTypes = {
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.number,
|
||||
delayValue: PropTypes.string,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
previousEnd: PropTypes.number,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@@ -147,13 +140,13 @@ CollapsedBlock.propTypes = {
|
||||
};
|
||||
|
||||
export default function EventBlock(props) {
|
||||
const { data, selected, delay, index, eventIndex, previousEnd, actionHandler } = props;
|
||||
const [collapsed] = useAtom(useMemo(() => SelectCollapse(data.id), [data.id]));
|
||||
const [, setCollapsed] = useAtom(HandleCollapse);
|
||||
const { data, selected, delay, index, eventIndex, previousEnd, actionHandler, next } = props;
|
||||
const { isCollapsed, setCollapsed } = useContext(CollapseContext);
|
||||
const collapsed = useMemo(() => isCollapsed(data.id), [data.id, isCollapsed]);
|
||||
|
||||
const isSelected = selected ? style.active : '';
|
||||
const isCollapsed = collapsed ? style.collapsed : style.expanded;
|
||||
const classSelect = `${style.event} ${isCollapsed} ${isSelected}`;
|
||||
const selectedStyle = selected ? style.active : '';
|
||||
const collapsedStyle = collapsed ? style.collapsed : style.expanded;
|
||||
const classSelect = `${style.event} ${collapsedStyle} ${selectedStyle}`;
|
||||
|
||||
// Calculate delay in min
|
||||
let delayValue = null;
|
||||
@@ -161,7 +154,7 @@ export default function EventBlock(props) {
|
||||
delayValue = `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))}`;
|
||||
}
|
||||
const handleCollapse = (isCollapsed) => {
|
||||
setCollapsed({ [data.id]: isCollapsed });
|
||||
setCollapsed(data.id, isCollapsed);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -177,7 +170,7 @@ export default function EventBlock(props) {
|
||||
<CollapsedBlock
|
||||
provided={provided}
|
||||
data={data}
|
||||
next={props.next}
|
||||
next={next}
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
previousEnd={previousEnd}
|
||||
@@ -188,7 +181,7 @@ export default function EventBlock(props) {
|
||||
provided={provided}
|
||||
eventIndex={eventIndex}
|
||||
data={data}
|
||||
next={props.next}
|
||||
next={next}
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
previousEnd={previousEnd}
|
||||
@@ -207,6 +200,7 @@ EventBlock.propTypes = {
|
||||
delay: PropTypes.number,
|
||||
index: PropTypes.number.isRequired,
|
||||
eventIndex: PropTypes.number.isRequired,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
previousEnd: PropTypes.number,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
next: PropTypes.bool,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiClock } from '@react-icons/all-files/fi/FiClock';
|
||||
@@ -23,8 +24,8 @@ export default function ActionButtons(props) {
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
backgroundColor='orange.200'
|
||||
color='orange.500'
|
||||
/>
|
||||
</Tooltip>
|
||||
<MenuList style={menuStyle}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createRef, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import style from './List.module.scss';
|
||||
import { createRef, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import Empty from 'common/state/Empty';
|
||||
import EventListItem from './EventListItem';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { memo, useCallback, useContext } from 'react';
|
||||
import DelayBlock from '../DelayBlock/DelayBlock';
|
||||
import BlockBlock from '../BlockBlock/BlockBlock';
|
||||
import EventBlock from '../EventBlock/EventBlock';
|
||||
import { memo, useCallback, useContext } from 'react';
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import { LocalEventSettingsContext } from '../../../app/context/LocalEventSettingsContext';
|
||||
|
||||
@@ -32,6 +32,16 @@ const EventListItem = (props) => {
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
||||
|
||||
/**
|
||||
* @description calculates duration from given options
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @returns {number}
|
||||
*/
|
||||
const calculateDuration = useCallback(
|
||||
(start, end) => (start > end ? end + 86400000 - start : end - start),
|
||||
[]
|
||||
);
|
||||
// Create / delete new events
|
||||
const actionHandler = useCallback(
|
||||
(action, payload) => {
|
||||
@@ -59,16 +69,27 @@ const EventListItem = (props) => {
|
||||
case 'update':
|
||||
// Handles and filters update requests
|
||||
const { field, value } = payload;
|
||||
const newData = { id: data.id };
|
||||
|
||||
if (field === 'durationOverride') {
|
||||
// duration defines timeEnd
|
||||
let end = (data.timeStart += value);
|
||||
const newData = { id: data.id, timeEnd: end };
|
||||
newData.timeEnd = data.timeStart += value;
|
||||
|
||||
// request update in parent
|
||||
eventsHandler('patch', newData);
|
||||
} else if (field === 'timeStart') {
|
||||
newData.duration = calculateDuration(value, data.timeEnd);
|
||||
newData.timeStart = value;
|
||||
// request update in parent
|
||||
eventsHandler('patch', newData);
|
||||
} else if (field === 'timeEnd') {
|
||||
newData.duration = calculateDuration(data.timeStart, value);
|
||||
newData.timeEnd = value;
|
||||
// request update in parent
|
||||
eventsHandler('patch', newData);
|
||||
} else if (field in data) {
|
||||
// create object with new field
|
||||
const newData = { id: data.id, [field]: value };
|
||||
newData[field] = value;
|
||||
|
||||
// request update in parent
|
||||
eventsHandler('patch', newData);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
||||
import { BatchOperation } from 'app/context/collapseAtom';
|
||||
import { useAtom } from 'jotai';
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import {
|
||||
fetchAllEvents,
|
||||
@@ -18,9 +16,10 @@ import { useFetch } from 'app/hooks/useFetch.js';
|
||||
import EventList from './EventList';
|
||||
import EventListMenu from 'features/menu/EventListMenu.jsx';
|
||||
import Empty from 'common/state/Empty';
|
||||
import { CollapseContext } from '../../../app/context/CollapseContext';
|
||||
|
||||
export default function EventListWrapper() {
|
||||
const [, setCollapsed] = useAtom(BatchOperation);
|
||||
const { expandAll, collapseMultiple } = useContext(CollapseContext);
|
||||
const queryClient = useQueryClient();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { data, status, isError, refetch } = useFetch(EVENTS_TABLE, fetchAllEvents);
|
||||
@@ -112,7 +111,11 @@ export default function EventListWrapper() {
|
||||
// Mutation finished, failed or successful
|
||||
// Fetch anyway, just to be sure
|
||||
onSettled: (newEvent) => {
|
||||
queryClient.invalidateQueries([EVENTS_TABLE, newEvent.id]);
|
||||
if (newEvent) {
|
||||
queryClient.invalidateQueries([EVENTS_TABLE, newEvent.id]);
|
||||
} else {
|
||||
queryClient.invalidateQueries(EVENTS_TABLE);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -231,6 +234,10 @@ export default function EventListWrapper() {
|
||||
if (options?.startIsLastEnd !== undefined) {
|
||||
newEvent.timeStart = data[options.startIsLastEnd].timeEnd || 0;
|
||||
}
|
||||
// hard coding duration value to be as expected for now
|
||||
// this until timeOptions gets implemented
|
||||
// Todo: implement duration options
|
||||
newEvent.duration = Math.max(0, newEvent.timeEnd - newEvent.timeStart) || 0;
|
||||
await addEvent.mutateAsync(newEvent);
|
||||
} catch (error) {
|
||||
emitError(`Error fetching data: ${error.message}`);
|
||||
@@ -297,11 +304,12 @@ export default function EventListWrapper() {
|
||||
|
||||
case 'collapseall':
|
||||
if (data == null) return;
|
||||
setCollapsed({ clear: true, items: data, isCollapsed: true });
|
||||
collapseMultiple(data);
|
||||
break;
|
||||
|
||||
case 'expandall':
|
||||
if (data == null) return;
|
||||
setCollapsed({ clear: true, items: data, isCollapsed: false });
|
||||
expandAll();
|
||||
break;
|
||||
|
||||
case 'deleteall':
|
||||
|
||||
Reference in New Issue
Block a user